友元声明的基本概念是很简单的:给予某个类或者函数访问友元声明所在的类的权利。然而,由于下面两个事实,这些简单概念却变得有些复杂:
- 友元声明可能是某个实体的唯一声明
- 友元函数的声明可以是一个定义
友元类的声明不能是类定义,因此友元类通常都不会出现问题。在引入模板之后,友元类声明的唯一变化是:可以声明一个特定的类模板实例为友元
template<typename T>
class Node;
template<typename T>
class Tree{
friend class Node<T>;
};
显然,如果要把类模板的实例声明为其他类(或者类模板)的友元,该类模板在声明的地方必须时可见的。然而,对一个普通类而言,就没有这个要求:
template<typename T>
class Tree{
friend class Factory; // 正确:即使这里是Factory的首次声明
friedn Class Node<T>; // 如果Node在此是不可见的,那么这条语句就是错误的
};
友元函数
通过确认紧接在友元函数名称后面的是一对<>
,我们可以把函数模板的实例声明为友元。<>
可以包含模板实参,也可以通过调用参数来演绎出实参,如果全部实参都能够通过演绎获得的话,那么<>
里面可以是空的。
template<typename T1, typename T2>
void combine(T1, T2);
class Mixer{
friend void combine<>(int&, int&); // 正确: T1 = int&, T2 =int&
friend void combine<int, int>(int, int); // 正确: T1 = int, T2 =int
friend void combine<char>(char, int); // 正确: T1 = char, T2 =int
friend void combine<char>(char&, int); //错误:不能匹配上面的combine()模板
friend void combine<>(long, long){...}; //错误:这里的友元声明不允许出现定义
};
另外,我们不能在友元声明中定义一个函数模板(我们最多只能定义一个特化);因此,命名一个实例的友元声明是不能作为定义的。
如果名称后面没有紧跟一对<>
,那么只有在下面两种情况是合法的:
- 如果名称不是受限的(也就是说,没有包含域运算符
::
),那么该名称一定不是(也不能)引用一个模板实例。如果在友元声明的地方,还看不到所匹配的非模板函数(即普通函数),那么这个友元声明就是函数的首次声明。于是,该声明可以是定义。 - 如果名称是受限的(也就是说,前面包含域运算符
::
),那么该名称必须引用一个在此之前声明的函数或者函数模板。在匹配的过程中,匹配的函数要优先于匹配的模板函数。注意,这里的友元声明不能是定义。
void multiply(void *) // 普通函数
template<typename T> // 函数模板
void multiply(T);
class Comrades{
friend void multiply(int){}; //可以在一个普通类里面定义一个新的友元函数:multiply(int)非受限函数名称,不能引用模板实例
friend void ::multiply(void *) // 引用上面的普通函数而非函数模板
friend void ::multiply(int); //引用一个模板实例
friend void ::friend <double *>(double*) //受限名称还可以具有一对<>,但模板在此必须是可见的
friend void ::error(){}; // 错误:受限的友元不能是一个定义
}
可以在类模板里面声明友元函数
template<typename T>
class Node{
Node<T> *allocate();
};
template<typename T>
class List{
friend Node<T> * Node<T>::allocate();
};
注意,因为对于任何只在模板内部声明的实体,都要等到模板被实例化之后,才会是一个具体的实体。在这之前该实体是不存在的。类模板的友元函数也是如此:
template<typename T>
class Creator{
friend void feed(Creator<T> *){ ... } //每个T都生成一个不同的::feed()函数
};
Creator<void> one; //生成::feed(Creator<void>*);
Creator<double> two; //生成::feed(Creator<double>*);
如上,每个Creator实例都生成了一个不同的feed()函数。另外,尽管这些函数是作为模板的一部分被生成的,但函数本身仍然是普通函数,而不是模板实例
最后:由于函数的实体处于类定义的内部,所以这些函数都是内联函数。因此,在两个不同的翻译单元可以生成相同的函数
友元模板
我们通常声明的友元只是:函数模板的实例或者类模板的实例,我们指定的友元也只是特定的实体。然而,有时候需要让模板的所有实例都成为友元,这就需要声明友元模板:
class Manager{
template<typename T>
friend class Task;
template<typename T>
friend void Schedule<T>::dispatch(Task<T>*);
template<typename T>
friend int ticket(){
return ++Manager::counter;
}
static int counter;
};
和普通友元的声明一样,只有在友元模板声明的是一个非受限的函数名称,并且后面没有紧跟<>
的情况下,该友元模板声明才能成为定义
友元模板声明的只是基本模板和基本模板的成员。当进行这些声明之后,与该基本模板相对应的模板局部特化和显示特化都会被自动看成友元