当前位置:嗨网首页>书籍在线阅读

24-模板类和友元

  
选择背景色: 黄橙 洋红 淡粉 水蓝 草绿 白色 选择字体: 宋体 黑体 微软雅黑 楷体 选择字体大小: 恢复默认

14.4.9 模板类和友元

模板类声明也可以有友元。模板的友元分3类:

  • 非模板友元;
  • 约束(bound)模板友元,即友元的类型取决于类被实例化时的类型;
  • 非约束(unbound)模板友元,即友元的所有具体化都是类的每一个具体化的友元。

下面分别介绍它们。

1.模板类的非模板友元函数

在模板类中将一个常规函数声明为友元:

template <class T>
class HasFriend
{
public:
    friend void counts();  // friend to all HasFriend instantiations
...
};

上述声明使counts()函数成为模板所有实例化的友元。例如,它将是类hasFriend和HasFriend的友元。

counts()函数不是通过对象调用的(它是友元,不是成员函数),也没有对象参数,那么它如何访问HasFriend对象呢?有很多种可能性。它可以访问全局对象;可以使用全局指针访问非全局对象;可以创建自己的对象;可以访问独立于对象的模板类的静态数据成员。

假设要为友元函数提供模板类参数,可以如下所示来进行友元声明吗?

friend void report(HasFriend &); // possible?

答案是不可以。原因是不存在HasFriend这样的对象,而只有特定的具体化,如HasFriend。要提供模板类参数,必须指明具体化。例如,可以这样做:

template <class T>
class HasFriend
{
    friend void report(HasFriend<T> &); // bound template friend
...
};

为理解上述代码的功能,想想声明一个特定类型的对象时,将生成的具体化:

HasFriend<int> hf;

编译器将用int替代模板参数T,因此友元声明的格式如下:

class HasFriend<int>
{
    friend void report(HasFriend<int> &); // bound template friend
    ...
};

也就是说,带HasFriend参数的report()将成为HasFriend类的友元。同样,带HasFriend参数的report()将是report()的一个重载版本——它是Hasfriend类的友元。

注意,report()本身并不是模板函数,而只是使用一个模板作参数。这意味着必须为要使用的友元定义显式具体化:

void report(HasFriend<short> &) {...}; // explicit specialization for short
void report(HasFriend<int> &) {...};   // explicit specialization for int

程序清单14.22说明了上面几点。HasFriend模板有一个静态成员ct。这意味着这个类的每一个特定的具体化都将有自己的静态成员。count()方法是所有HasFriend具体化的友元,它报告两个特定的具体化(HasFriend和HasFriend)的ct的值。该程序还提供两个report()函数,它们分别是某个特定HasFriend具体化的友元。

程序清单14.22 frnd2tmp.cpp

// frnd2tmp.cpp -- template class with non-template friends
#include <iostream>
using std::cout;
using std::endl;
template <typename T>
class HasFriend
{
private:
    T item;
    static int ct;
public:
    HasFriend(const T & i) : item(i) {ct++;}
    ~HasFriend() {ct--; }
    friend void counts();
    friend void reports(HasFriend<T> &); // template parameter
};
// each specialization has its own static data member
template <typename T>
int HasFriend<T>::ct = 0;
// non-template friend to all HasFriend<T> classes
void counts()
{
    cout << "int count: " << HasFriend<int>::ct << "; ";
    cout << "double count: " << HasFriend<double>::ct << endl;
}
// non-template friend to the HasFriend<int> class
void reports(HasFriend<int> & hf)
{
    cout <<"HasFriend<int>: " << hf.item << endl;
}
// non-template friend to the HasFriend<double> class
void reports(HasFriend<double> & hf)
{
    cout <<"HasFriend<double>: " << hf.item << endl;
}
int main()
{
    cout << "No objects declared: ";
    counts();
    HasFriend<int> hfi1(10);
    cout << "After hfi1 declared: ";
    counts();
    HasFriend<int> hfi2(20);
    cout << "After hfi2 declared: ";
    counts();
    HasFriend<double> hfdb(10.5);
    cout << "After hfdb declared: ";
    counts();
    reports(hfi1);
    reports(hfi2);
    reports(hfdb);
    return 0;
}

有些编译器将对您使用非模板友元发出警告。下面是程序清单14.22所示程序的输出:

No objects declared: int count: 0; double count: 0
After hfi1 declared: int count: 1; double count: 0
After hfi2 declared: int count: 2; double count: 0
After hfdb declared: int count: 2; double count: 1
HasFriend<int>: 10
HasFriend<int>: 20
HasFriend<double>: 10.5

2.模板类的约束模板友元函数

可以修改前一个示例,使友元函数本身成为模板。具体地说,为约束模板友元做准备,要使类的每一个具体化都获得与友元匹配的具体化。这比非模板友元复杂些,包含以下3步。

首先,在类定义的前面声明每个模板函数。

template <typename T> void counts();
template <typename T> void report(T &);

然后,在函数中再次将模板声明为友元。这些语句根据类模板参数的类型声明具体化:

template <typename TT>
class HasFriendT
{
...
    friend void counts<TT>();
    friend void report<>(HasFriendT<TT> &);
};

声明中的<>指出这是模板具体化。对于report(),<>可以为空,因为可以从函数参数推断出如下模板类型参数:

HasFriendT<TT>

然而,也可以使用:

report<HasFriendT<TT> >(HasFriendT<TT> &)

但counts()函数没有参数,因此必须使用模板参数语法()来指明其具体化。还需要注意的是,TT是HasFriendT类的参数类型。

同样,理解这些声明的最佳方式也是设想声明一个特定具体化的对象时,它们将变成什么样。例如,假设声明了这样一个对象:

HasFriendT<int> squack;

编译器将用int替换TT,并生成下面的类定义:

class HasFriendT<int>
{
...
    friend void counts<int>();
    friend void report<>(HasFriendT<int> &);
};

基于TT的具体化将变为int,基于HasFriend的具体化将变为HasFriend。因此,模板具体化counts()和report<HasFriendT >()被声明为HasFriendT类的友元。

程序必须满足的第三个要求是,为友元提供模板定义。程序清单14.23说明了这3个方面。请注意,程序清单14.22包含1个count()函数,它是所有HasFriend类的友元;而程序清单14.23包含两个count()函数,它们分别是某个被实例化的类类型的友元。因为count()函数调用没有可被编译器用来推断出所需具体化的函数参数,所以这些调用使用count和coount()指明具体化。但对于report()调用,编译器可以从参数类型推断出要使用的具体化。使用<>格式也能获得同样的效果:

report<HasFriendT<int> >(hfi2); // same as report(hfi2);

程序清单14.23 tmp2tmp.cpp

// tmp2tmp.cpp -- template friends to a template class
#include <iostream>
using std::cout;
using std::endl;
// template prototypes
template <typename T> void counts();
template <typename T> void report(T &);
// template class
template <typename TT>
class HasFriendT
{
private:
    TT item;
    static int ct;
public:
    HasFriendT(const TT & i) : item(i) {ct++;}
    ~HasFriendT() { ct--; }
    friend void counts<TT>();
    friend void report<>(HasFriendT<TT> &);
};
template <typename T>
int HasFriendT<T>::ct = 0;
// template friend functions definitions
template <typename T>
void counts()
{
    cout << "template size: " << sizeof(HasFriendT<T>) << "; ";
    cout << "template counts(): " << HasFriendT<T>::ct << endl;
}
template <typename T>
void report(T & hf)
{
    cout << hf.item << endl;
}
int main()
{
    counts<int>();
    HasFriendT<int> hfi1(10);
    HasFriendT<int> hfi2(20);
    HasFriendT<double> hfdb(10.5);
    report(hfi1); // generate report(HasFriendT<int> &)
    report(hfi2); // generate report(HasFriendT<int> &)
    report(hfdb); // generate report(HasFriendT<double> &)
    cout << "counts<int>() output:\n";
    counts<int>();
    cout << "counts<double>() output:\n";
    counts<double>();
    return 0;
}

下面是程序清单14.23所示程序的输出:

template size: 4; template counts(): 0
10
20
10.5
counts<int>() output:
template size: 4; template counts(): 2
counts<double>() output:
template size: 8; template counts(): 1

正如您看到的,counts和counts报告的模板大小不同,这表明每种T类型都有自己的友元函数count()。

3.模板类的非约束模板友元函数

前一节中的约束模板友元函数是在类外面声明的模板的具体化。int类具体化获得int函数具体化,依此类推。通过在类内部声明模板,可以创建非约束友元函数,即每个函数具体化都是每个类具体化的友元。对于非约束友元,友元模板类型参数与模板类类型参数是不同的:

template <typename T>
class ManyFriend
{
...
    template <typename C, typename D> friend void show2(C &, D &);
};

程序清单14.24是一个使用非约束友元的例子。其中,函数调用show2(hfi1,hfi2)与下面的具体化匹配:

void show2<ManyFriend<int> &, ManyFriend<int> &>
          (ManyFriend<int> & c, ManyFriend<int> & d);

因为它是所有ManyFriend具体化的友元,所以能够访问所有具体化的item成员,但它只访问了ManyFriend对象。

同样,show2(hfd, hfi2)与下面具体化匹配:

void show2<ManyFriend<double> &, ManyFriend<int> &>
          (ManyFriend<double> & c, ManyFriend<int> & d);

它也是所有ManyFriend具体化的友元,并访问了ManyFriend对象的item成员和ManyFriend对象的item成员。

程序清单14.24 manyfrnd.cpp

// manyfrnd.cpp -- unbound template friend to a template class
#include <iostream>
using std::cout;
using std::endl;
template <typename T>
class ManyFriend
{
private:
    T item;
public:
    ManyFriend(const T & i) : item(i) {}
    template <typename C, typename D> friend void show2(C &, D &);
};
template <typename C, typename D> void show2(C & c, D & d)
{
    cout << c.item << ", " << d.item << endl;
}
int main()
{
    ManyFriend<int> hfi1(10);
    ManyFriend<int> hfi2(20);
    ManyFriend<double> hfdb(10.5);
    cout << "hfi1, hfi2: ";
    show2(hfi1, hfi2);
    cout << "hfdb, hfi2: ";
    show2(hfdb, hfi2);
    return 0;
}

程序清单14.24所示程序的输出如下:

hfi1, hfi2: 10, 20
hfdb, hfi2: 10.5, 20