问题
我们知道,指针有一个很好的点,就是能够在类继承层次之间支持隐式转换,derived class 指针能够被隐式转换为 base class 指针,如下面这样:
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
,想要模拟上述的转换过程:
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>
我们要明白,具有继承关系的两个类型,base class 和 derived class,分别作为模板参数具现化某个模板类型时,生成的具体模板类并不具有 base-derived 的关系。比如上面的 SmartPtr<Top>
, SmartPtr<Middle>
和 SmartPtr<Bottom>
之间就没有任何继承关系。
我们首先可能会想到,那就针对每一种可能的转换写一个相应的拷贝构造和拷贝赋值函数不就好了。但是我们应该能够清楚的看出来,我们永远无法写出所有我们需要的所有构造函数,因为模板类能够被传递的模板类型是任意的。而且,退一步讲,即使你能写出来,那如果模板类参数的继承体系有所改变,相对应的智能指针对应的转换也会需要改变,这当然也不是我们希望做的。
成员模板函数
为解决上面提出来的问题,我们就要用到成员模板函数,像下面这样:
template<typename T>
class SmartPtr{
public:
explicit SmartPtr(T* realPtr);
template<typename U>
SmartPtr(const SmartPtr<U>& other); //成员模板拷贝构造,或者叫泛化的拷贝构造
//...
};
这样的话,对于任意类型 U 和 T,就可以根据 SmartPtr<U>
生成一个 SmartPtr<T>
有一点可能需要注意一下,就是我们并没有将这个泛化的拷贝构造声明为 explicit
,因为基础指针之间的转换就是一种隐式的转换,所以这里我们当然也希望能够实现隐式的转换。
这里又有一个问题,就是我们希望能够将 SmartPtr<Middle>
隐式转换为 SmartPtr<Top>
,但是我们不希望将 SmartPtr<Top>
转换为 SmartPtr<Middle>
,所以可能会有下面的实现版本:
template<typename T>
class SmartPtr{
public:
explicit SmartPtr(T* realPtr);
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*
的指针。那当且仅当它们之间能够发生隐式转换时,才能够通过编译,而这正是我们想要的。
相应的,这种成员模板函数并不仅限于使用 copy construction,也常常用来支持 assignment operation。
还有一点要注意,这种泛化的拷贝构造并不会阻止编译器为我们生成 non-template 的拷贝构造。