Friend 声明语句的基本概念很简单:指定某些classes或functions,让他们可以对friend声明语句所在的class进行特权存取。但是下面两个事实使这个概念复杂化了:
1)friend声明的语句可能是某一entity的唯一声明;
2)friend函数声明可以就是其定义。
friend class 声明语句不能成为一个定义式,这就大大降低了问题的发生。涉及template时,唯一需要考虑的是新增情况:你可以把某个class template的实例声明为friend:
template <typename T>
class Node;
template <typename T>
class Tree{
friend class Node<T>;
...
};
在class template的某一实体成为其它class或class template的friend之前,class template Node必须已经被声明可见。但对常规class来说,没有这个限制。
template <typename T>
class Tree{
friend class Factory; //OK,即使是Factory的首次声明
friend class Node<T>; // Error(如果没有声明过Node)
}
1.友元函数Friend Function
Function template的具体实现实例可以成为别人的一个friend function。前提是该function template名称之后必须紧跟着以角括号括起的自变量列或者说实参列表,如果编译器可推导出所有自变量,自变量列表可以为空。
template <typename T1, typename T2>
void combine(T1,T2);
class Mixer
{
friend void combine<>(int&, int&); //OK, T1=int&, T2= int&
friend void combine<int, int>(int,int); //OK; T1 = int, T2 = int
friend void combine<char>(char, int); //OK,T1= char, T2 = int
friend void combine<char>(char& ,int); //Error, not match
friend void combine<>(long,long) {...} //Error, 不能在此处定义函数
}
注意,我们无法定义一个template实体instance,最多只能定义一个特化体specailization。因此一个令某实体获得名称的friend声明语句,不能够是个定义式。
如果friend名称之后没有跟着角括号,有两种可能:
1)如果这不是个资格修饰名称(不含::),就绝不会引用某个template实体;
2)如果这是一个资格修饰名称(含有::),它就必定引用一个先前已经定义的function或function template。
以下例子有助于理解各种可能情况:
void multiply(void*) //ordinary function
template <typename T>
void multiply(T); //function template
class Comrades
{
friend void multiply(int) {} //定义了一个新函数::multiply(int)
friend void ::multiply(void*); //引用先前的常规函数
friend void ::multiply(int);
friend void ::multiply<double*>(double*); //修饰名称可以带角括号,但编译器必须见到模板
friend void ::error() {} //Error:带修饰资格的friend,不能是个定义。
}
如果把friend声明于class templates中,先前所说的规则也全部适用,而且template parameter可参与到friend function之内:
template <typename T>
class Node {
Node<T>* allocate();
...
};
template <typename T>
class List {
friend Node<T>* Node<T>::allocate();
...
};
然而把friend function定义于class template中可能会引发一个有趣的错误因为任何只在template中被声明的object,都是直到template被实例化后才能成为具体实体concrete entity。考虑下面例子:
template <typename T>
class Creator {
friend void appear() { //定义一个新函数
...
}
};
Creator<void > mirache; //::appear()此时生成
Creator<double> oops; //Error: 试图再次生成appear()
在这个例子中,两个不同的具体实现产生出两个完全相同的定义,违反ODR原则。
因此,我们必须确保class template的 template parameters出现在定义于该tamplate之内的所有friend functions的类型中。现在对先前的例子做些改动:
template <typename T>
class Creator {
friend void feed(Creator<T>*) {
...
}
};
Creator<void> one; //产生:::feed(Creator<void>*)
Creator<double> two; //产生:::feed(Creator<double>*)
这个例子中每个Creator都实例化产出一个不同的函数。注意,虽然这些函数是在template实例化过程中产生的,但它们还是常规函数,不是某个模板的实体。
2.友元模板Friend Templates
通常当我们定义一个friend,而它是个function或是个class template时,我们可以明确指定以哪一个物体entity作为friend。但有时候把一个template的所有实体都指定为某个class的friend也相当有用。这是经由一种所谓的friend template机制实现的。例如,
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;
};
和常规friend声明语句一样,只有当friend template产生一个无修饰函数名(unqulified function name),而且该名称之后不紧跟角括号,这个friend template才可以是一个定义式。
Friend template 只能声明primary template及primary templates的成员。任何与该primary template相关的偏特化和明确特化体都将自动被编译器视为friends。