智能指针的行为像是指针,但是没有提供加的功能。例如,Item 13中解释了如何使用标准auto_ptr和tr1::shared_ptr指针在正确的时间自动删除堆上的资源。STL容器中的迭代器基本上都是智能指针:当然,你不能通过使用“++”来将链表中的指向一个节点的内建指针移到下一个节点上去,但是list::iterator可以这么做。
1. 问题分析——如何实现智能指针的隐式转换
真正的指针能够做好的一件事情是支持隐式转换。派生类指针可以隐式转换为基类指针,指向非const的指针可以隐式转换成为指向const对象的指针,等等。例如,考虑可以在一个三层继承体系中发生的转换:
1 class Top { ... }; 2 class Middle: public Top { ... }; 3 class Bottom: public Middle { ... }; 4 Top *pt1 = new Middle; // convert Middle* ⇒ Top* 5 6 Top *pt2 = new Bottom; // convert Bottom* ⇒ Top* 7 8 const Top *pct2 = pt1; // convert Top* ⇒ const Top*
在用户自定义的智能指针中模仿这种转换是很微妙的。我们想让下面的代码通过编译:
1 template2 class SmartPtr { 3 public: // smart pointers are typically 4 explicit SmartPtr(T *realPtr); // initialized by built-in pointers 5 ... 6 }; 7 SmartPtr pt1 = // convert SmartPtr ⇒ 8 SmartPtr(new Middle); // SmartPtr 9 SmartPtr pt2 = // convert SmartPtr ⇒ 10 SmartPtr(new Bottom); // SmartPtr 11 SmartPtr<const Top> pct2 = pt1; // convert SmartPtr ⇒ 12 // SmartPtr
同一个模板的不同实例之间没有固有的关系,所以编译器将SmartPtr
在上面的智能指针示例代码中,每个语句都创建了一个新的智能指针对象,所以现在我们把焦点放在如何实现出一个行为表现如我们所愿的智能指针构造函数。关键的一点是没有办法实现我们需要的所有构造函数。在上面的继承体系中,我们可以用一个SmartPtr
1 class BelowBottom: public Bottom { ... };
我们将会需要支持用SmartPtr
2. 使用成员函数模板——泛化拷贝构造函数进行隐式转换
从原则上来说,我们所需要的构造函数的数量是没有限制的。既然模板可以被实例化成为没有限制数量的函数,因此看上去我们不需要一个SmartPtr的构造函数,我们需要的是一个构造函数模板。这样的模板是成员函数模板(member function templates) (也被叫做member templates)的一个例子——也即是为类产生成员函数的模板:
1 template2 class SmartPtr { 3 public: 4 template // member template 5 SmartPtr(const SmartPtr& other); // for a ”generalized 6 7 ... // copy constructor” 8 9 };
这就是说对于每个类型T和每个类型U,一个SmartPtr
2.1 隐式转换不需要explicit
上面的泛化拷贝构造函数并没有被声明为explicit。这是经过仔细考虑的。内建指针类型之间的类型转换(例如从派生类转换到基类指针)是隐式的,并且不需要cast,因此智能指针模仿这种行为就是合理的。在模板化的构造函数上省略explicit正好做到了这一点。
2.2 将不符合要求的模板实例化函数剔除掉
为SmartPtr实现的泛化拷贝构造函数比我们想要的提供了更多的东西。我们想要用SmartPtr
假设SmartPtr遵循auto_ptr和tr1::shared_ptr的设计,也提供一个get成员函数来返回智能指针对象所包含的内建类型指针的一份拷贝(Item 15),我们可以使用构造函数模板的实现来对一些转换进行限制:
1 template2 class SmartPtr { 3 public: 4 template 5 SmartPtr(const SmartPtr& other) // initialize this held ptr 6 : heldPtr(other.get()) { ... } // with other’s held ptr 7 T* get() const { return heldPtr; } 8 ... 9 private: // built-in pointer held 10 T *heldPtr; // by the SmartPtr 11 }
我们在成员初始化列表中用SmartPtr中包含的类型为U*的指针来初始化SmartPtr
3. 成员函数模板对赋值的支持
成员函数模板的使用不仅仅限定在构造函数上。它们的另外一个普通的角色是对赋值的支持。例如,tr1的shared_ptr(Item 13)支持用所有兼容的内建指针来对其进行构造,可以用tr1::shared_ptr,auto_ptr和tr1::weak_ptr(Item 54)来进行构造,对赋值也同样使用,但是tr1::weak_ptr例外。下面是从tr1的说明中摘录下来的tr1::shared_ptr的实现,可以看到在声明模板参数的时候它倾向于使用class而不是typename。(Item 42中描述的,在这个上下文中它们的意义相同。)
1 template<class T> class shared_ptr { 2 public: 3 4 template<class Y> // construct from 5 6 explicit shared_ptr(Y * p); // any compatible 7 8 template<class Y> // built-in pointer, 9 10 11 shared_ptr(shared_ptrconst& r); // shared_ptr, 12 template<class Y> // weak_ptr, or 13 14 explicit shared_ptr(weak_ptr const& r); // auto_ptr 15 16 template<class Y> 17 18 explicit shared_ptr(auto_ptr & r); 19 20 template<class Y> // assign from 21 shared_ptr& operator=(shared_ptr const& r); // any compatible 22 template<class Y> // shared_ptr or 23 shared_ptr& operator=(auto_ptr & r); // auto_ptr 24 ... 25 };
所有的这些构造函数都是explicit的,除了泛化拷贝构造函数。这就意味着从shared_ptr的一种类型隐式转换到shared_ptr的另一种类型是允许的,但是内建类型指针和其他的智能指针类型到shared_ptr的隐式转换是禁止的。(显示的转换是可以的(例如通过使用cast))。同样有趣的是传递给tr1::shared_ptr构造函数和赋值运算符的auto_ptr没有被声明为const,但是tr1::shared_ptr和tr1::weak_ptr的传递却声明为const了。这是因为auto_ptr被拷贝的时候已经被修改了(Item 13)。
4. 成员函数模板会生成默认拷贝构造函数
成员函数模板是美好的东西,但是它们没有修改语言的基本规则。Item 5解释了编译器会自动生成的4个成员函数中的两个函数为拷贝构造函数和拷贝赋值运算符。Tr1::shared_ptr声明了一个泛化拷贝构造函数,很清楚的是如果类型T和类型Y是相同的,泛化拷贝构造函数就会被实例化成一个“普通”的拷贝构造函数。那么编译器会为tr1::shared_ptr生成一个拷贝构造函数么?或者说用相同类型的tr1::shared_ptr构造另外一个tr1::shared_ptr的时候,编译器会实例化泛化拷贝构造函数么?
正如我所说的,成员模板没有修改语言的规则。“如果你需要一个拷贝构造函数而你没有自己声明,编译器会为你生成一个”这条规则也是其中之一。在一个类中声明一个泛化拷贝构造函数(一个member template)不会阻止编译器生成它们自己的拷贝构造函数(non-template),所以如果你想控制拷贝构造函数的所有方面,你必须同时声明一个泛化拷贝构造函数和“普通的”构造函数。对于赋值同样适用。下面是tr1::shared_ptr的定义:
1 template<class T> class shared_ptr { 2 public: 3 shared_ptr(shared_ptr const& r); // copy constructor 4 5 template<class Y> // generalized 6 7 8 9 shared_ptr(shared_ptrconst& r); // copy constructor 10 11 shared_ptr& operator=(shared_ptr const& r); // copy assignment 12 13 template<class Y> // generalized 14 15 16 shared_ptr& operator=(shared_ptr const& r); // copy assignment 17 ... 18 };
5. 总结
- 使用成员函数模板来生成接受所有兼容类型的函数。
- 如果你为泛化拷贝构造函数和泛化赋值运算符声明成员模板,你同样需要声明普通的拷贝构造函数和拷贝赋值运算符。