友元工厂是一种模式,从类模板的每一次实例化中合成一个非模板、非成员函数 —— 每次模板被实例化为一种新类型时,就会生成一个新的函数。该函数的参数可以使用在该类模板实例化中能够声明的类型。这个类型就是类本身,但也可以是模板所知晓的类型。
以这种方式,友元工厂可以与我们在第8章中研究的模式结合使用。回想一下,CRTP 的主要思想是:一个类从一个基类模板的实例化中继承,而该基类模板的参数正是派生类的类型。考虑一下我们在这里拥有的情况 —— 一个基类模板,会随着每一个从它派生的类而自动实例化,并且知道那个类型是什么。这似乎正是放置友元工厂的理想位置。
当然,由基类生成的运算符不仅需要知道它们所操作对象的类型,还需要知道如何处理该对象(例如,如何打印它)。有时,所需的数据实际上存在于基类中,基类就可以提供完整的实现。但派生类对基类的添加如此之少的情况是罕见的。
CRTP 与友元工厂更常见的结合用法是:通过某种其他功能以标准方式实现某些运算符。例如,operator!=() 可以通过 operator==() 来实现:
// Example 19
template <typename D> class B {
public:
friend bool operator!=(const D& lhs, const D& rhs) {
return !(lhs == rhs);
}
};
template <typename T> class C : public B<C<T>> {
T x_;
public:
C(T x) : x_(x) {}
friend bool operator==(const C& lhs, const C& rhs) {
return lhs.x_ == rhs.x_;
}
};
派生类 C 使用友元工厂模式,直接从类模板的实例化中为二元运算符 operator==() 生成一个非模板函数。同时,也继承自基类 B,这也会触发该模板的实例化,进而为每一个我们已经生成了 operator==() 的类型,生成一个对应的 operator!=() 的非模板函数。
CRTP 的第二种用途是将成员函数转换为非成员函数。例如,二元运算符 operator+() 有时会基于 operator+=() 来实现,而 operator+=() 总是一个成员函数(作用于其第一个操作数)。为了实现二元 operator+(),需要有人来处理向该对象类型的转换,然后才能调用 operator+=()。可以通过友元工厂的通用 CRTP 基类所生成的二元运算符,来进行这些转换。
同样地,如果约定类都具有一个 print() 成员函数,那么插入运算符(operator<<)也可以生成:
// Example 20
template <typename D> class B {
public:
friend D operator+(const D& lhs, const D& rhs) {
D res(lhs);
res += rhs; // 将 += 转化为 +
return res;
}
friend std::ostream&
operator<<(std::ostream& out, const D& d) {
d.print(out);
return out;
}
};
template <typename T> class C : public B<C<T>> {
T x_;
public:
C(T x) : x_(x) {}
C operator+=(const C& incr) {
x_ += incr.x_;
return *this;
}
void print(std::ostream& out) const {
out << x_;
}
};
通过这种方式,CRTP 可以用来添加样板化的接口,同时将具体的实现委托给派生类。毕竟,它本质上是一种静态的(编译时)委托模式。