【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*pt1newMiddle;// ✅ 合法: 派生类指针转基类指针Top*pt2newBottom;// ✅ 合法constTop*pctpt1;// ✅ 合法: 非常量指针转常量指针然而对于模板类情况就不同了。编译器将同一个模板的不同具体体现视为完全独立的、无关联的类型。例如SmartPtrMiddle和SmartPtrTop在编译器看来就像string和vector一样毫无关系因此它们之间无法进行隐式转换。templatetypenameTclassSmartPtr{public:explicitSmartPtr(T*realPtr);};SmartPtrToppt1SmartPtrMiddle(newMiddle);// ❌ 编译错误!这就导致了智能指针等模板类在使用上不如原生指针方便。解决方案成员函数模板为了解决上述问题条款45引入了成员函数模板。1. 使用成员函数模板接受所有兼容类型通过在类模板内部定义一个成员函数模板尤其是构造函数可以让它接受所有兼容的类型。templatetypenameTclassSmartPtr{public:// 原始的、针对特定类型T的构造函数explicitSmartPtr(T*realPtr):heldPtr(realPtr){}// 关键的泛化拷贝构造函数这是一个成员函数模板templatetypenameUSmartPtr(constSmartPtrUother):heldPtr(other.get()){// 核心: 利用U*到T*的隐式转换}// 提供get函数获取持有的原始指针T*get()const{returnheldPtr;}private:T*heldPtr;};它是如何工作的?“万能插座”这个成员模板构造函数说“只要你给我的SmartPtrU对象其内部指针U*能隐式转换为我需要的T*我就能用它来构造一个SmartPtrT对象。”自动类型安全检查关键在heldPtr(other.get())这行初始化代码。编译器会尝试将U*转换为T*。只有当转换合法时例如U是T的派生类编译才会通过。这保证了转换的安全性避免了不合理的类型转换如double*转int*会报错。现在之前的代码就可以工作了SmartPtrToppt1SmartPtrMiddle(newMiddle);// ✅ 现在合法!SmartPtrconstToppctpt1;// ✅ 也合法了!2. 仍需声明正常的拷贝构造和赋值操作符这是一个非常关键且容易遗漏的细节。为什么需要?成员函数模板不会阻止编译器自动生成默认的拷贝构造函数和拷贝赋值运算符。当你进行同类型拷贝时例如SmartPtrTop p1(p2)编译器更倾向于使用它自动生成的默认拷贝构造函数而不是你提供的泛化模板版本。如果你需要在拷贝时执行特殊逻辑例如智能指针的引用计数管理而只提供了泛化模板那么同类型拷贝将不会执行你的特殊逻辑。正确的做法是同时提供templatetypenameTclassSmartPtr{public:// ... 其他成员 ...// 1. 正常的、针对同类型的拷贝构造函数SmartPtr(constSmartPtrother):heldPtr(other.heldPtr){// ... 可能需要进行引用计数操作 ...}// 2. 泛化的拷贝构造函数 (用于兼容类型转换)templatetypenameUSmartPtr(constSmartPtrUother):heldPtr(other.get()){// ... 可能需要进行引用计数操作 ...}// 对拷贝赋值运算符同理SmartPtroperator(constSmartPtrother);templatetypenameUSmartPtroperator(constSmartPtrUother);};标准库中的std::shared_ptr就是严格遵循这一模式实现的。