Effective C++手册Item 45: 用 member function templates(成员函数模板) 接受 "all compatible types"(“所有兼容类型”)

Item 45: 用 member function templates(成员函数模板) 接受 "all compatible types"(“所有兼容类型”)

作者:Scott Meyers

译者:fatalerror99 (iTePub's Nirvana)

发布:http://blog.csdn.net/fatalerror99/

smart pointers(智能指针)是行为很像指针但是增加了指针没有提供的功能的 objects。例如,Item 13 阐述了标准 auto_ptr 和 tr1::shared_ptr 是怎样被应用于在恰当的时间自动删除的 heap-based resources(基于堆的资源)的。STL containers 内的 iterators(迭代器)几乎始终是 smart pointers(智能指针);你绝对不能指望用 "++" 将一个 built-in pointer(内建指针)从一个 linked list(线性链表)的一个节点移动到下一个,但是 list::iterators 可以做到。

real pointers(真正的指针)做得很好的一件事是支持 implicit conversions(隐式转换)。derived class pointers(派生类指针)隐式转换到 base class pointers(基类指针),pointers to non-const objects(指向非常量对象的指针)转换到 pointers to const objects(指向常量对象的指针),等等。例如,考虑在一个 three-level hierarchy(三层继承体系)中能发生的一些转换:

class Top { ... };
class Middle: public Top { ... };
class Bottom: public Middle { ... };
Top *pt1 = new Middle;                   // convert Middle* => Top*
Top *pt2 = new Bottom;                   // convert Bottom* => Top*
const Top *pct2 = pt1;                   // convert Top* => const Top*

在 user-defined smart pointer classes(用户定义智能指针类)中模仿这些转换是需要技巧的。我们要让下面的代码能够编译:

template
class SmartPtr {
public:                             // smart pointers are typically
  explicit SmartPtr(T *realPtr);    // initialized by built-in pointers
  ...
};
SmartPtr pt1 =                 // convert SmartPtr =>
SmartPtr(new Middle);       // SmartPtr
SmartPtr pt2 =                 // convert SmartPtr =>
SmartPtr(new Bottom);       // SmartPtr
SmartPtr pct2 = pt1;     // convert SmartPtr =>
                                    // SmartPtr

在同一个 template(模板)的不同 instantiations(实例化)之间没有 inherent relationship(继承关系),所以编译器认为 SmartPtr 和 SmartPtr 是完全不同的 classes,并不比(比方说)vector 和 Widget 的关系更近。为了得到我们想要的在 SmartPtr classes 之间的转换,我们必须显式地为它们编程。

在上面的 smart pointer(智能指针)的示例代码中,每一个语句创建一个新的 smart pointer object(智能指针对象),所以现在我们就集中于我们如何写 smart pointer constructors(智能指针的构造函数),让它以我们想要的方式运转。一个关键的事实是我们无法写出我们需要的全部 constructors(构造函数)。在上面的 hierarchy(继承体系)中,我们能从一个 SmartPtr 或一个 SmartPtr 构造出一个 SmartPtr,但是如果将来这个 hierarchy(继承体系)被扩充,SmartPtr objects 还必须能从其它 smart pointer types(智能指针类型)构造出来。例如,如果我们后来加入

class BelowBottom: public Bottom { ... };

我们就需要支持从 SmartPtr objects 到 SmartPtr objects 的创建,而且我们当然不希望为了做到这一点而必须改变 SmartPtr template。

大体上,我们需要的 constructors(构造函数)的数量是无限的。因为一个 template(模板)能被实例化而产生无数个函数,所以好像我们不需要为 SmartPtr 提供一个 constructor function(构造函数函数),我们需要一个 constructor template(构造函数模板)。这样的 templates(模板)是 member function templates(成员函数模板)(常常被恰如其分地称为 member templates(成员模板))——生成一个 class 的 member functions(成员函数)的 templates(模板)的范例:

template
class SmartPtr {
public:
  template                       // member template
  SmartPtr(const SmartPtr& other);        // for a "generalized
  ...                                        // copy constructor"
};

这就是说对于每一种类型 T 和每一种类型 U,都能从一个 SmartPtr 创建出一个 SmartPtr,因为 SmartPtr 有一个取得一个 SmartPtr 参数的 constructor(构造函数)。像这样的 constructor(构造函数)——从一个类型是同一个 template(模板)的不同实例化的 object 创建另一个 object 的 constructor(构造函数)(例如,从一个 SmartPtr 创建一个 SmartPtr)——有时被称为 generalized copy constructors(泛型化拷贝构造函数)。

上面的 generalized copy constructor(泛型化拷贝构造函数)没有被声明为 explicit(显式)的。这是故意为之的。built-in pointer types(内建指针类型)之间的类型转换(例如,从派生类指针到基类指针)是隐式的和不需要 cast(强制转型)的,所以让 smart pointers(智能指针)模仿这一行为是合理的。在 templatized constructor(模板化构造函数)中省略 explicit 正好做到这一点。

作为声明,SmartPtr 的 generalized copy constructor(泛型化拷贝构造函数)提供的东西比我们想要的还多。是的,我们需要能够从一个 SmartPtr 创建一个 SmartPtr,但是我们不需要能够从一个 SmartPtr 创建一个 SmartPtr,这就像颠倒 public inheritance(公有继承)的含义(参见 Item 32)。我们也不需要能够从一个 SmartPtr 创建一个 SmartPtr,因为这和从 int* 到 double* 的 implicit conversion(隐式转换)是不相称的。我们必须设法过滤从这个 member template(成员模板)生成的 member functions(成员函数)的群体。

假如 SmartPtr 跟随 auto_ptr 和 tr1::shared_ptr 的脚步,提供一个返回被这个 smart pointer(智能指针)持有的 built-in pointer(内建指针)的拷贝的 get member function(get 成员函数)(参见 Item 15),我们可以用 constructor template(构造函数模板)的实现将转换限定在我们想要的范围:

template
class SmartPtr {
public:
  template
  SmartPtr(const SmartPtr& other)         // initialize this held ptr
  : heldPtr(other.get()) { ... }             // with other's held ptr
  T* get() const { return heldPtr; }
  ...
private:                                     // built-in pointer held
  T *heldPtr;                                // by the SmartPtr
};

我们通过 member initialization list(成员初始化列表),用 SmartPtr 持有的类型为 U* 的指针初始化 SmartPtr 的类型为 T* 的 data member(数据成员)。这只有在“存在一个从一个 U* 指针到一个 T* 指针的 implicit conversion(隐式转换)”的条件下才能编译,而这正是我们想要的。最终的效果就是 SmartPtr 现在有一个 generalized copy constructor(泛型化拷贝构造函数),它只有在传入一个 compatible type(兼容类型)的参数时才能编译。

member function templates(成员函数模板)的用途并不限于 constructors(构造函数)。它们的另一个常见的任务是用于支持 assignment(赋值)。例如,TR1 的 shared_ptr(再次参见 Item 13)支持从所有兼容的 built-in pointers(内建指针),tr1::shared_ptrs,auto_ptrs 和 tr1::weak_ptrs(参见 Item 54)构造,以及从除 tr1::weak_ptrs 以外所有这些赋值。这里是从 TR1 规范中摘录出来的一段关于 tr1::shared_ptr 的内容,包括它在声明 template parameters(模板参数)时使用 class 而不是 typename 的偏好。(就像 Item 42 中阐述的,在这里的上下文环境中,它们的含义严格一致。)

template class shared_ptr {
public:
  template                                     // construct from
    explicit shared_ptr(Y * p);                         // any compatible
  template                                     // built-in pointer,
    shared_ptr(shared_ptr const& r);                 // shared_ptr,
  template                                     // weak_ptr, or
    explicit shared_ptr(weak_ptr const& r);          // auto_ptr
  template
    explicit shared_ptr(auto_ptr& r);
  template                                     // assign from
    shared_ptr& operator=(shared_ptr const& r);      // any compatible
  template                                     // shared_ptr or
    shared_ptr& operator=(auto_ptr& r);              // auto_ptr
  ...
};

除了 generalized copy constructor(泛型化拷贝构造函数),所有这些 constructors(构造函数)都是 explicit(显式)的。这就意味着从 shared_ptr 的一种类型到另一种的 implicit conversion(隐式转换)是被允许的,但是从一个 built-in pointer(内建指针)或其 smart pointer type(智能指针类型)的 implicit conversion(隐式转换)是不被许可的。(explicit conversion(显式转换)——例如,经由一个 cast(强制转型)——还是可以的。)同样引起注意的是 auto_ptrs 被传送给 tr1::shared_ptr 的 constructors(构造函数)和 assignment operators(赋值操作符)的方式没有被声明为 const,于此对照的是 tr1::shared_ptrs 和 tr1::weak_ptrs 的被传送的方式。这是 auto_ptrs 被复制时需要独一无二的被改变的事实的一个必然结果(参见 Item 13)。

member function templates(成员函数模板)是一个极好的东西,但是它们没有改变这个语言的基本规则。Item 5 阐述的编译器可以产生的四个 member functions(成员函数)其中两个是 copy constructor(拷贝构造函数)和 copy assignment operator(拷贝赋值运算符)。tr1::shared_ptr 声明了一个 generalized copy constructor(泛型化拷贝构造函数),而且很明显,当类型 T 和 Y 相同时,generalized copy constructor(泛型化拷贝构造函数)就能被实例化而成为 "normal" copy constructor(“常规”拷贝构造函数)。那么,当一个 tr1::shared_ptr object 从另一个相同类型的 tr1::shared_ptr object 构造时,编译器是为 tr1::shared_ptr 生成一个 copy constructor(拷贝构造函数),还是实例化 generalized copy constructor template(泛型化拷贝构造函数模板)?

就像我说过的,member templates(成员模板)不改变语言规则,而且规则规定如果一个 copy constructor(拷贝构造函数)是必需的而你没有声明,将为你自动生成一个。在一个 class 中声明一个 generalized copy constructor(泛型化拷贝构造函数)(一个 member template(成员模板))不会阻止编译器生成它们自己的 copy constructor(拷贝构造函数)(非模板的),所以如果你要全面支配 copy construction(拷贝构造),你必须既声明一个 generalized copy constructor(泛型化拷贝构造函数)又声明一个 "normal" copy constructor(“常规”拷贝构造函数)。这同样适用于 assignment(赋值)。这是从 tr1::shared_ptr 的定义中摘录的一段,可以作为例子:

template class shared_ptr {
public:
  shared_ptr(shared_ptr const& r);                 // copy constructor
  template                                // generalized
    shared_ptr(shared_ptr const& r);            // copy constructor
  shared_ptr& operator=(shared_ptr const& r);      // copy assignment
  template                                // generalized
    shared_ptr& operator=(shared_ptr const& r); // copy assignment
  ...
};

Things to Remember

  • 使用 member function templates(成员函数模板)生成接受所有兼容类型的函数。

  • 如果你为 generalized copy construction(泛型化拷贝构造)或 generalized assignment(泛型化赋值)声明了 member templates(成员模板),你依然需要声明 normal copy constructor(常规拷贝构造函数)和 copy assignment operator(拷贝赋值运算符)。