C++中模板类和模板类的友元函数
http://www.cnblogs.com/assemble8086/archive/2011/10/02/2198308.html有详细介绍,下面只讲什么时候模板类会实例化以及模板类中成员函数实例化和友元函数的使用。
模板类的使用实现了编译时多态性,避免了运行时多态性的消耗(虚指针和虚函数表),但是编译时多态和运行时多态并不冲突,编译时多态是根据传入模板的对象类型实现不同的操作完成,比如继承同一接口的不同类型可以在编译时根据调用接口的队形类型实现多态,而运行时多态则根据虚函数来实现,必须等到运行时才能判断真正运行的接口。
当模板类中使用虚函数时,模板实例化的时候必须实例化器虚函数表,一旦虚函数表实例化就必须实例化其全部虚函数。
引起模板类的实例化的主要情形有以下几种:
在我们使用类模板时,只有当代码中使用了类模板的一个实例的名字,而且上下文环境要求必须存在类的定义时,这个类模板才被实例化。并不是每次使用一个类都要求知道该类的定义。
(1)声明一个类模板的指针和引用(不是初始化或者new时),不会引起类模板的实例化,因为没有必要知道该类的定义。例如:
- <span style="font-family:'Microsoft YaHei';">class Matrix;
- Matrix *pm;//不需要类的定义
- void inverse(Matrix &);//也不需要类的定义</span>
以及
- <span style="font-family:'Microsoft YaHei';">void foo(Queue<int> &qi) //引用声明
- {
- Queue<int> *pqi = &qi; //不会实例化
- //...
- }</span>
但是如果检查这个指针或引用所值的那个对象时,类模板才会被实例化。比如在上例中,如果指针pqi被解引用,qi被用来获得它所指向的对象值,或者pqi或qi被用来访问Queue<int>的数据成员或成员函数时,Queue<int>才会被实例化。
- <span style="font-family:'Microsoft YaHei';">void foo(Queue<int> &qi)
- {
- Queue<int> *pqi = &qi;
- //因为成员函数被调用,所以Queue<int>被实例化
- pqi ->add(255);
- //...
- }</span>
(2)定义一个类类型的对象时需要该类的定义,因此类模板会被实例化、例如:
- <span style="font-family:'Microsoft YaHei';">class Matrix;
- Matrix obj1;//Error
- class Matrix{...};
- Matrix obj1;//OK</span>
下面的例子中,对象qi的定义引起类模板Queue<int>被实例化:
- <span style="font-family:'Microsoft YaHei';">Queue<int> qi;</span>
(3)在使用sizeof()时,它是计算对象的大小,编译器必须根据类型将其实例化出来,所以类模板被实例化:
- <span style="font-family:'Microsoft YaHei';">int iobj = sizeof(Stack<string>);</span>
(4)new表达式要求类模板被实例化,或者引用初始化的时候实例化。
Queue<int> *p_qi = new Queue<int>;
(5)引用类模板中的成员会导致类模板被编译器实例化。
(6)需要注意的是,类模板的成员函数本身也是一个模板。标准C++要求这样的成员函数只有在被调用或者取地址的时候,才被实例化。(在标准C++之前有些编译器在实例化类模板时,就实例化类模板的成员函数。)用来实例化成员函数的类型,就是其成员函数要调用的那个类对象的类型。
(7)类模板中的友元模板函数是非成员函数,当然类模板的实例化不会影响友元模板函数的实例化,只有调用友元模板函数的时候才会实例化,要保证参数类型严格相符,否则找不到定义,除非友元模板函数是inline函数。
(8)在模板类未实例化之前,编译器不会对任何类成员的类型进行检查,定义声明使用不是该模板类的成员编译器也不会报错,也就是说,模板类并不知道自己究竟拥有多少成员拥有什么成员,只有实例化后才会进行检查。
----------------------------------------------------------------------------------------------------------------------------------------------------------
模板类的友元分3类:
非模板友元
约束(bound)模板友元,即友元的类型取决于类被实例化时的类型。
非约束(undound)模板友元,即友元的所有具体化都是类的每一个具体化的友元。
1.模板类的非模板友元函数
template<class T>
class HasFriend
{ friend void counts(); //friend to all HaFriend instantiations
... };
上诉声明使counts()函数成为模板所有实例化的友元。
counts()函数不是通过对象调用的,也没有对象参数。它可以访问全局对象;可以使用全局指针访问非全局对象;可以创建自己的对象;可以访问独立于对象的模板类的静态数据成员。
为友元函数提供模板类参数,必须提供特定的具体化,如HasFriend<short>。
要提供模板类参数,必须指明具体化:
template<class T>
class HasFriend
{ friend void report(HasFriend<T> & ); // bound template friend
... };
也就是说,带HasFriend<int>参数的report()将成为HasFriend<int>类的友元函数。同样,带HasFriend<double>参数的report()将是report()的一个重载版本——它将是HasFriend<double>类的友元。
report()本省并不是模板函数,而只是使用一个模板作参数。这意味着必须为要使用的友元定义显示具体化:
void report(HasFriend<short> & ) {...} //explicit specialization for short
void report(HasFriend<int> & ) {...} //explicit specialization for int
2.模板类的约束模板友元函数
修改前一个上面的范例,使友元函数本身成为模板。使类的每一个具体化都获得与友元匹配的具体化。包含以下三步:
首先,在类定义的前面声明每个模板函数:
template <typename T> void counts();
template <typename T> void report(T &);
然后在函数中再次将模板声明为友元。这些语句根据类模板参数的类型声明具体化:
template<class TT>
class HasFriend
{ friend void counts<TT>();
friend void report<>(HasFriend<TT> &);
... };
上述声明中的<>指出这是模板具体化。对于report(),<>可以为空,这是因为可以从函数参数推断出模板类型参数(HasFriend<TT>)。不过,也可以使用report<HasFriend<TT> >(HasFriend<TT> &)。但counts()函数没有参数,因此必须使用模板参数句法(<TT>)来指明其具体化。还需要注意的是,TT是HasFriend类的参数类型。
否则链接失败。
当然也可以指定类型的函数,例如 friend void report<char>(char &);
假设声明了这样一个对象:
HasFriend<int> squack;
则编译器将用int替换TT,并生成下面的类定义:
class HasFriend<int>
{ friend void counts<int>();
friend void report<>(HasFriend<int> &);
... };
于是,模板具体化counts<int>()和report<HasFriend<int> >()被声明为HasFriend<int>类的友元。
注意:类模板的实例化不会实例化一个友元函数,只是声明友元而不实例化。
最后,程序必须为友元提供模板定义有两种方式,
一种在类内声明定义,另一种先声明然后再类外定义。例如:
template <typename T>
void counts() { cout << HasFriend<T>::x << endl;}
template <typename T>
void report(T & hf) {cout << hf.x << endl;}
该方式为类外定义。
而类内声明定义不用在类前声明模板函数,而是直接在类内定义:
template<class TT>
class HasFriend
{ friend void counts(){...}
friend void report(HasFriend<TT> &){...}
... };
注意:这里没有<>,因为这表示友元函数是模板函数,这种情况下,友元函数随着模板类实例化而实例化(inline)。
调用时,counts()需要提供具体化参数类型。report()只需直接调用,例如:
counts<int>(); //调用HasFriend<int>的友元函数
HasFriend<double> hfdb;
report(hfdb); //调用HasFriend<double>的友元函数,此时实例化友元函数。
3.模板类的非约束模板友元函数
通过在类内部声明友元函数模板,可以创建非约束友元函数,即每个函数具体化都是每个类具体化的友元:
template <typename T>
class ManyFriend
{...
template <typename C,typename D> friend void show(C &,D &);
};
在类外定义该友元:
template <typename C,typename D>
void show(C & c,D & d){ ... }
假如创建ManyFriend<int>类对象(hfi1)和ManyFriend<double>类对象(hfi2),传递给show(),那么编译器将生成如下具体化定义:
void show<ManyFriend<int> &,ManyFriend<double> &> (ManyFriend<int> & c,ManyFriend<double> & d){ ... }//使用时才实例化