所谓智能指针是“行为像指针”的对象,并提供指针没有的机能。
真实指针做的很好的一件事是,支持隐式转换。Derived class指针可以隐式转换为base class指针。指向non-const的对象的指针可以转换为指向const对象。下面是发生于三层继承体系的一些转换:
class Top{...};
class Middle: public Top{...};
class Bottom: public Middle{...};
Top* pt1 = new Middle; //将Middle*转换成Top*
Top* pt2 = new Bottom; //将Bottom*转换成Top*
const Top* pct2 = pt1; //将Top*转换成const Top*但想在用户自定义的智能指针中模拟以上转换,就有点麻烦,我们希望以下代码通过编译:
SmartPtr<Top> pt1 = SmartPtr<Middle>(new Middle); //将SmartPtr<Middle>转换为SmartPtr<Top>
SmartPtr<Top> pt2 = SmartPtr<Bottom>(new Bottom); //将SmartPtr<Bottom>转换为SmartPtr<Top>
SmartPtr<const Top> pct2 = pt1; //将SmartPtr<Top>转换为SmartPtr<const Top>但是同一个template的不同具现体之间并不存在什么与生俱来的固有关系(如果带有base-derived关系的B,D两类型分别具现化某个template,产生出的两个具现体并不带有base-derived关系),所以编译器视SmartPtr<Middle>和SmartPtr<Top>为完全不同的classes。为了获得我们希望的SmartPtr class之间的转换能力,我们必须将它们明确地编写出来。
Template和泛型编程(Generic Programming)
我们需要的不是写一个构造函数,根据一个SmartPtr<Middle>或一个SmartPtr<Bottom>构造一个SmartPtr<Top>。而是为它写一个构造模板。这样的模板是member function templates(常简称为member templates),其作用是为class生成函数:
template<typename T>
class SmartPtr{
public:
template<typename U>//member template
SmartPtr(const SmartPtr<U>& other);//为了生成copy构造函数
};以上代码的意思是,任何类型T和任何类型U,可以根据SmarPtr<U>生成一个SmartPtr<T>——因为SmartPtr<T>有个构造函数接受一个SmartPtr<U>参数。这一类构造函数通过对象u创建对象t(例如根据对象SmartPtr<U>创建一个SmartPtr<T>),我们称之为泛化copy构造函数。
并未声明为explicit,那是蓄意的,在模板化构造函数中略去explicit是为了效仿隐式转换。
完成声明之后,我们必须从某方面对这一member template所创建的成员函数群进行拣选或删除。我们希望根据一个SmartPtr<bottom>创建一个SmartPtr<top>,却不希望根据一个SmartPtr<top>创建一个SmartPtr<bottom>,因为那对public继承而言那是矛盾的;也不希望根据一个SmartPtr<double>创建一个SmartPtr<int>,因为现实中并没有“将double*转换成为int*”的对应隐式转换行为。
代码中约束转换行为,使它符合我们的期望:
template<typename T>
class SmartPtr{
template<typename U>
SmartPtr(const SmartPtr<U>& other)
:heldPtr(other.get()){...}//以other的heldPtr初始化this的heldPtr
T* get() const {return heldPtr;}
private:
T* heldPtr;//持有的内置(原始)指针
};成员初值列中,以类型为U*的指针初始化类型为T*成员变量,这个行为只有当“存在某个隐式转化可将一个U*指针转换为一个T*指针”时才能通过编译,而那正是我们想要的。SmartPtr<T>有了一个泛化copy构造函数,这个构造函数只在其所获得的实参隶属适当(兼容)类型时才通过编译。
member function templates的效用不限于构造函数,它们常扮演的另一个角色是支持赋值操作。例如TR1的shared_ptr:
template<class T>
class shared_ptr{
public:
template<class Y> //构造,来自任何兼容的
explicit shared_ptr(Y* p); //内置指针
template<class Y>
shared_ptr(shared_ptr<Y> const& r); //或shared_ptr
template<class Y>
explicit shared_ptr(weak_ptr<Y> const& r); //或weak_ptr
template<class Y>
explicit shared_ptr(auto_ptr<Y> & r); //或auto_ptr
template<class Y> //赋值来自任何兼容的
shared_ptr& operator=(shared_ptr<Y> const& r); //shared_ptr
template<class Y>
shared_ptr& operator=(auro_ptr<Y>& r); //或auto_ptr
};上述构造函数除了“泛化copy构造函数”外,都是explicit,那意味着从某个shared_ptr类型隐式转换成另一个shared_ptr类型是被允许的,但从某个内置类型指针或从其他智能指针类型进行隐式转换则不被认可auto_ptr并未被声明为const,因为条款13说过,当你复制一个auto_ptr时,它们其实被改动了。
member function templates并不改变语言的基本规则。条款5说过,编译器可能为我们产生4个成员函数,其中两个是copy构造和copy assignment操作符。现在,shared_ptr声明了一个泛化copy构造函数,而显然一旦类型T和Y相同,泛化copy构造函数会被具现化为“正常的”copy构造函数。那么编译器是会暗自为shared_ptr生成一个copy构造函数呢?还是当某个shared_ptr对象根据另一个同型的shared_ptr对象展开构造行为时,编译器会将“泛化copy构造函数模板”具现化呢?
member templates并不改变语言规则,而语言规则说,如果程序需要一个copy构造函数,你却没声明它,编译器会为你暗自生成一个。声明一个泛化copy构造函数(是个member template)并不阻止编译器生成它们自己的copy构造函数(一个non-template),所以如果你想要控制copy构造函数的方方面面,你必须同时声明泛化copy构造函数和“正常的”copy构造函数。相同规则也适用于赋值操作符。
template<class T>
class shared_ptr{
public:
shared_ptr(shared_ptr const& r);//copy 构造函数
template<class Y>
shared_ptr(shared_ptr<Y> const& r);//泛化copy构造函数
shared_ptr& operator=(shared_ptr const& r); //copy assignment
template<class Y>
shared_ptr& operator=(shared_ptr<Y> const& r);//泛化copy assignment
};