条款45:运用成员函数模板接受所有兼容类型
与智能指针相比,普通内置指针的一大优点是支持隐式转换。
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*
在智能指针类中模拟这种转换就很棘手。
template<typename T>
class SmartPtr {
public:
explicit SmartPtr(T* realPtr); // 智能指针通常由内置指针初始化
...
};
//同一个模板的不同实例之间没有内在的联系,因此编译器将SmartPtr<Middle>和SmartPtr<Top>视为完全不同的类
// SmartPtr<Middle> 转换为 SmartPtr<Top>
SmartPtr<Top> pt1 = SmartPtr<Middle>(new Middle);
// SmartPtr<Bottom> 转换为 SmartPtr<Top>
SmartPtr<Top> pt2 = SmartPtr<Bottom>(new Bottom);
// SmartPtr<Top> 转换为 SmartPtr<const Top>
SmartPtr<const Top> pct2 = pt1;
通过构造函数模板,更好的体现对象间的继承关系:
template<typename T>
class SmartPtr {
public:
template<typename U> // “构造函数”模板,成员函数模板
//SmartPtr<T>可以从SmartPtr<U>创建
SmartPtr(const SmartPtr<U>& other); // 注意:是non-explicit
...
};
我们还需要控制转换的方向:
template<typename T>
class SmartPtr {
public:
template<typename U>
SmartPtr(const SmartPtr<U>& other) //用其他对象的保存指针初始化这个对象
: heldPtr(other.get()) {
...
}
//只有当存在从U*指针到T*指针的隐式转换时,才会编译通过
T* get() const { return heldPtr; }
...
private:
T* heldPtr; // SmartPtr持有的内置指针
};
成员函数模板的用途不仅限于构造函数。
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); // 或unique_ptr
template<class Y>
shared_ptr& operator=(shared_ptr<Y> const& r); //赋值
template<class Y>
shared_ptr& operator=(auto_ptr<Y>& r);
...
};
声明泛化的拷贝构造函数(成员模板)不会阻止编译器生成自己的拷贝构造函数(非模板),想控制拷贝构造的方方面面,必须同时声明泛化和“普通”版本的拷贝构造函数。赋值也一样。
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); // 泛化的拷贝赋值
...
};
总结:
- 使用成员函数模板生成接受所有兼容类型的函数。
- 如果为泛化的拷贝构造函数或赋值运算符声明成员模板,还需要正常声明普通的版本。