真实指针做得很好的一件事是,它支持隐式转换,如下:
class Top { ... };
class Middle: public Top{ ... };
class Bottom: public Middle { ... };
Top* pt1 = new Middle; //将Middle*转换为Top*
Middle* pt2 = new Bottom; //将Bottom*转换为Middle*
const Top* pct2 = pt1; //将Top*转换为const Top*
我们希望智能指针也能实现像真实指针这样的自动转换,即我们可以像下面那样转换智能指针:
template<typename T>
class SmartPtr{
public:
explicit SmartPtr( T* realPtr ); //以内置指针完成初始化
...
};
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>
为此我们可以为SmartPtr编写构造函数,使其行为能够满足我们的转型需要。可是似乎我们无法写出我们需要的所有构造函数,因为一旦一个继承体系被扩充,相当于我们需要添加一个构造函数。我们必须为它写一个构造模板。这样的模板是所谓member function template(成员函数模板),其作用是为class生成函数:
template<typename T>
class SmartPtr{
public:
template<typename U>
SmartPtr( const SmartPtr<U>& other )
...
};
这段代码的意思是,对任何类型T和任何类型U,这里可以根据SmartPtr生成一个SmartPtr。这样的函数我们称之为泛化copy构造函数。
但是,我们并不希望根据一个SmartPtr生成一个SmartPtr,这不符合逻辑。我们可以在“构造模板”实现代码中约束转换行为:
template<typename T>
class SmartPtr{
public:
//这样如果两者不兼容,不支持隐式转换,编译会报错。
template<typename T>
SmartPtr( const SmartPtr<U>& other )
: heldPtr(other.get()){ }
T* get() const{ return heldPtr; }
private:
T* heldPtr;
...
};
member function template的效用不限于构造函数,它们常扮演的另一个角色是支持赋值操作。另外,member function template并不改变语言基本规则。如果程序需要一个copy构造函数,而你没有声明它,编译器会为你暗自生成一个。在class内声明泛化copy构造函数并不会阻止编译器生成它们自己的copy构造函数(non-template的)。因此如果你想要控制copy构造函数的方方面面,你必须同时声明泛化构造函数和“正常的”copy构造函数。相同规则也适用于赋值操作。