更多 C++ 惯用法/结交新朋友
外观
简化类模板友元函数的创建。
友元函数通常用于为类提供一些辅助的附加接口。例如,插入 (<<)、提取 (>>) 运算符和重载的算术运算符通常是友元。与为非模板类声明友元函数相比,声明类模板的友元函数要复杂一些。当涉及模板时,类及其友元之间存在四种关系。
- 一对多:非模板函数可以是所有模板类实例化的友元。
- 多对一:模板函数的所有实例化可以是普通非模板类的友元。
- 一对一:使用一组模板参数实例化的模板函数可以是使用相同模板参数实例化的一组模板类的友元。这也是普通非模板类和普通非模板友元函数之间的关系。
- 多对多:模板函数的所有实例化可以是模板类所有实例化的友元。
这里我们感兴趣的是一对一关系,因为它在 C++ 中需要额外的语法。下面是一个例子。
template<typename T>
class Foo {
T value;
public:
Foo(const T& t) { value = t; }
friend ostream& operator<<(ostream&, const Foo<T>&);
};
template<typename T>
ostream& operator<<(ostream& os, const Foo<T>& b) {
return os << b.value;
}
上面的例子对我们来说是不合适的,因为插入器不是模板,但它仍然使用模板参数 (T)。这是一个问题,因为它不是成员函数。operator<<( ) 必须是模板,以便为每个 T 创建不同的特化。
这里的解决方案是在类声明之前的友元声明之前声明一个插入运算符模板,并在友元声明中添加 <>。它表示先前声明的模板应该成为友元。
// Forward declarations
template<class T> class Foo;
template<class T> ostream& operator<<(ostream&,
const Foo<T>&);
template<class T>
class Foo {
T value;
public:
Foo(const T& t) { value = t; }
friend ostream& operator<< <>(ostream&, const Foo<T>&);
};
template<class T>
ostream& operator<<(ostream& os, const Foo<T>& b)
{
return os << b.value;
}
上述解决方案的缺点是它非常冗长。
Dan Saks 建议了另一种方法来克服上述解决方案的冗长性。他的解决方案被称为“结交新朋友”惯用法。这个想法是在类模板内部定义友元函数,如下所示。
template<typename T>
class Foo {
T value;
public:
Foo(const T& t) { value = t; }
friend ostream& operator<<(ostream& os, const Foo<T>& b)
{
return os << b.value;
}
};
这样的友元函数不是模板,但模板充当“制造”新朋友的工厂。为每个 Foo 的特化创建一个新的非模板函数。