【effective c++】条款四十五:运用成员函数模板接受所有兼容类型
文章目录
- Effective C++ 条款45:运用成员函数模板接受所有兼容类型
- 核心问题:模板类的"死板"
- 解决方案:成员函数模板
- 1. 使用成员函数模板接受所有兼容类型
- 2. 仍需声明正常的拷贝构造和赋值操作符
请记住:
1. 请使用 member function templates(成员函数模板) 生成“可接受所有兼容类型” 的函数
2. 如果你声明 member templates 用于“泛化 copy 构造” 或“泛化 assignment操作”。 你还是需要声明正常的 copy构造函数和 copy assignment 操作符。
Effective C++ 条款45:运用成员函数模板接受所有兼容类型
核心问题:模板类的"死板"
在C++中,原生指针非常灵活,支持多种隐式转换:
- 向上转型:派生类指针可以隐式转换为基类指针(如
Derived*转Base*)。 - 常量化:非常量指针可以转换为常量指针(如
int*转const int*)。
classTop{};classMiddle:publicTop{};classBottom:publicMiddle{};Top*pt1=newMiddle;// ✅ 合法: 派生类指针转基类指针Top*pt2=newBottom;// ✅ 合法constTop*pct=pt1;// ✅ 合法: 非常量指针转常量指针然而,对于模板类,情况就不同了。编译器将同一个模板的不同具体体现视为完全独立的、无关联的类型。例如,SmartPtr<Middle>和SmartPtr<Top>在编译器看来就像string和vector一样毫无关系,因此它们之间无法进行隐式转换。
template<typenameT>classSmartPtr{public:explicitSmartPtr(T*realPtr);};SmartPtr<Top>pt1=SmartPtr<Middle>(newMiddle);// ❌ 编译错误!这就导致了智能指针等模板类在使用上不如原生指针方便。
解决方案:成员函数模板
为了解决上述问题,条款45引入了成员函数模板。
1. 使用成员函数模板接受所有兼容类型
通过在类模板内部定义一个成员函数模板(尤其是构造函数),可以让它接受所有兼容的类型。
template<typenameT>classSmartPtr{public:// 原始的、针对特定类型T的构造函数explicitSmartPtr(T*realPtr):heldPtr(realPtr){}// 关键的"泛化拷贝构造函数":这是一个成员函数模板template<typenameU>SmartPtr(constSmartPtr<U>&other):heldPtr(other.get()){// 核心: 利用U*到T*的隐式转换}// 提供get函数获取持有的原始指针T*get()const{returnheldPtr;}private:T*heldPtr;};它是如何工作的?
- “万能插座”:这个成员模板构造函数说:“只要你给我的
SmartPtr<U>对象,其内部指针U*能隐式转换为我需要的T*,我就能用它来构造一个SmartPtr<T>对象。” - 自动类型安全检查:关键在
heldPtr(other.get())这行初始化代码。编译器会尝试将U*转换为T*。只有当转换合法时(例如U是T的派生类),编译才会通过。这保证了转换的安全性,避免了不合理的类型转换(如double*转int*会报错)。
现在,之前的代码就可以工作了:
SmartPtr<Top>pt1=SmartPtr<Middle>(newMiddle);// ✅ 现在合法!SmartPtr<constTop>pct=pt1;// ✅ 也合法了!2. 仍需声明正常的拷贝构造和赋值操作符
这是一个非常关键且容易遗漏的细节。
为什么需要?
- 成员函数模板不会阻止编译器自动生成默认的拷贝构造函数和拷贝赋值运算符。
- 当你进行同类型拷贝时(例如
SmartPtr<Top> p1(p2)),编译器更倾向于使用它自动生成的默认拷贝构造函数,而不是你提供的泛化模板版本。 - 如果你需要在拷贝时执行特殊逻辑(例如智能指针的引用计数管理),而只提供了泛化模板,那么同类型拷贝将不会执行你的特殊逻辑。
正确的做法是同时提供:
template<typenameT>classSmartPtr{public:// ... 其他成员 ...// 1. 正常的、针对同类型的拷贝构造函数SmartPtr(constSmartPtr&other):heldPtr(other.heldPtr){// ... 可能需要进行引用计数操作 ...}// 2. 泛化的拷贝构造函数 (用于兼容类型转换)template<typenameU>SmartPtr(constSmartPtr<U>&other):heldPtr(other.get()){// ... 可能需要进行引用计数操作 ...}// 对拷贝赋值运算符同理SmartPtr&operator=(constSmartPtr&other);template<typenameU>SmartPtr&operator=(constSmartPtr<U>&other);};标准库中的std::shared_ptr就是严格遵循这一模式实现的。
