引入
所谓智能指针就是“行为像指针”的对象,并提供指针没有的功能。
真实指针做的很好的以减轻是支持隐式转换。派生类指针可以隐式转换为基类指针,指向non-const对象的指针可以转为指向const的对象…:
class Top;
class Middle : public Top {...}
class Bottom : public Middle {...}
Top * tp1 = new Middle; // Middle* ---> Top*
Top * tp2 = new Bottom; // Bottom* ---> Top *
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<Top> pt2 = SmartPtr<Middle>(new Bottom);
SmartPtr<const Top> pt2 pct2 = pt1;
但是,同一个模板的不同具现体之间没有关系(如果以带有base-derived关系的B,D分别具现化某个模板,产生出来的灵感具现体没有base-derive关系)。所以,为了好的SmartPtr类之间的转换能力,我们必须将它们明确的编写出来。
模板和泛型编程
在上述智能指针实例中,每一个语句创建了一个新式智能指针对象,所以限制我们应该关注如何编写智能指针的构造函数,使其行为能够满足我们的转型需要。
又因为我们需要的构造函数数量没有止尽(因为一个模板可以被无限具现化),所以我们需要的不是为SmartPtr写一个构造函数,而是为它写一个构造模板。这样的模板叫做成员函数模板,其作用是为类生成函数:
template<typename T>
class SmartPtr{
public:
template<typename U>
SmartPtr(const SmartPtr<U>& other); // 为了生成拷贝构造函数
};
上面代码的意思是,对任何类型T和任何类型U,这里可以根据SmartPtr<U>
生成一个SmartPtr<T>
---- 因为SmartPtr<T>
有一个构造函数接受一个SmartPtr<U>
参数。这一类构造函数根据对象u创建对象t,而u和v的类型是同一个模板的不同实现体,有时我们称之为泛化拷贝构造函数。
上面的泛化拷贝构造函数并非声明为explicit。这是故意的,因为原始指针类型之间的转换(比如派生类指针—>基类指针)是隐式转换,无需明白写出转型动作。在模板化的构造函数中不写explicit也是这个道理。
完成声明后,这个为SmartPtr而写的"泛化构造函数"提供的东西比我们需要的多:我们希望根据SmartPtr< Bottom>创建SmartPtr< Top>,但是不希望根据一个SmartPtr< Top> 创建一个SmartPtr< Bottom>,因为这对public继承是矛盾的。不希望根据一个SmartPtr< double> 创建一个SmartPtr< int>,因为现实没有“将int转换为double”的隐式转换行为。即我们必须从某方面对这一成员函数模板所创建的成员函数群进行筛选和删除。
加入SmartPtr遵循shared_ptr提供的榜样,也提供一个get成员函数,返回智能指针对象所持有的那个原始指针的副本。那么我们可以在“构造函数”实现代码中的约束行为,使它符合我们的期望:
template<typename T>
class SmartPtr{
public:
template<typename U>
SmartPtr(const SmartPtr<U>& other)
: heldPtr(other.get()){ ...}
T* get() const { return heldPtr;};
private:
T* heldPtr; // 这个SmartPtr持有的原始指针
};
上面heldPtr(other.get())
只有当“存在某个隐式转换可将可以U指针转换为T指针”才能通过---- 现在SmatrPtr< T>有了一个泛化拷贝构造函数,这个构造函数只有在其所获得的实参隶属于兼容类型时才能通过编译。
成员函数模板的效用不限于构造函数,它们常扮演的另一个角色是支持赋值操作。比如shared_ptr支持所有来自“兼容的指针、shared_ptr、auto_ptr、weak_ptr”的构造行为,以及所有来自“兼容的指针、shared_ptr、auto_ptr”的赋值操作。
template<class T>
class shared_ptr{
public:
template<class Y> //构造,来自任何兼容的
explicit shared_ptr(T *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<T>const & r); // shared_ptr
template<class Y> //赋值,来自任何兼容的
shared_ptr& operator=(auto_ptr<T>& r); // shared_ptr
...
}
上面所有的构造函数除了“泛化拷贝函数”外都是explicit,这意味着从某个shared_ptr类型隐式转换为另一个shared_ptr类型是被运行的,但从某个内置指针或者从其他智能指针进行隐式转换不可(但是显式转换比如cast强制转型可以)。
另外,传递给构造函数和=运算符的auto_ptr并未声明为const,但是shared_ptr是const。这是因为,当你复制一个auto_ptr时,它们被改动了。
如果程序需要一个拷贝构造函数,但你没有声明它,编译期会暗中生成一个。在类声明泛化拷贝构造函数(是个成员模板)并不会阻止编译期生成它们自己的拷贝构造函数(非模板),所以如果你想要控制拷贝构造的方方面面,以必须同时声明泛化拷贝构造函数和非泛化拷贝构造函数。赋值操作也是如此:
template<class T>
class shared_ptr{
public:
shared_ptr<shared_ptr const& r>;
template<class Y>
shared_ptr(shared_ptr<Y>const & r);
shared_ptr& operator=(shared_ptr const &r);
template<class Y>
shared_ptr & operator=(shared_ptr<Y>const & r);
};
总结
- 请使用成员函数模板生成“可以接受所有兼容类型”的函数
- 如果你声明成员模板用于“泛化拷贝构造”或者“泛化赋值运算符”,还是需要声明正常的拷贝构造函数和赋值运算符