Effective C++ 条款45:运用成员函数模板接受所有兼容类型
Effective C++ 条款45:运用成员函数模板接受所有兼容类型
核心思想:使用成员函数模板(member function templates)生成可接受兼容类型的函数,特别是泛型拷贝构造函数和赋值操作符,同时避免抑制编译器生成的默认特殊成员函数。
⚠️ 1. 智能指针的类型转换问题
问题根源:
- 希望智能指针能模拟内置指针的隐式转换(如派生类指针到基类指针)
- 模板实例化后不同模板参数生成的是不同类,无法直接转换
- 需要为每种可能的兼容类型单独编写构造函数,不现实
错误示例:
template<typename T>
class SmartPtr {
public:explicit SmartPtr(T* realPtr); // 原始指针构造函数// 希望支持以下转换,但无法实现:// SmartPtr<Base> = SmartPtr<Derived>// SmartPtr<const T> = SmartPtr<T>
};
🚨 2. 成员函数模板解决方案
解决方案:
- 声明成员函数模板(泛型拷贝构造函数)
- 使用类型约束确保安全转换
优化实现:
template<typename T>
class SmartPtr {
public:template<typename U>SmartPtr(const SmartPtr<U>& other) // 泛型拷贝构造: heldPtr(other.get()) { } // 使用get()获取原始指针T* get() const { return heldPtr; } // 获取原始指针private:T* heldPtr; // 持有原始指针
};
类型安全约束:
- 添加编译期类型检查,确保U可隐式转换为T
template<typename T>
class SmartPtr {
public:template<typename U, typename = std::enable_if_t<std::is_convertible_v<U*, T*>>>SmartPtr(const SmartPtr<U>& other): heldPtr(other.get()) { }// ...
};
⚖️ 3. 赋值操作与兼容性处理
问题场景:
- 需要支持不同类型的智能指针赋值
- 同时要避免编译器自动生成默认拷贝操作
解决方案:
- 为赋值操作定义成员模板函数
- 显式声明普通拷贝操作以避免被模板隐藏
完整实现:
template<typename T>
class SmartPtr {
public:// 泛型拷贝构造template<typename U, typename = std::enable_if_t<std::is_convertible_v<U*, T*>>>SmartPtr(const SmartPtr<U>& other);// 显式声明普通拷贝构造和赋值(防止被模板隐藏)SmartPtr(const SmartPtr&); SmartPtr& operator=(const SmartPtr&);// 泛型赋值操作符template<typename U, typename = std::enable_if_t<std::is_convertible_v<U*, T*>>>SmartPtr& operator=(const SmartPtr<U>& other);// ... 其他成员 ...
};
注意事项:
- 成员函数模板不改变语言规则:拷贝构造函数不会阻止编译器生成默认拷贝构造函数
- 若需要完全控制,可显式定义或使用
=default
/=delete
💡 关键设计原则
-
使用成员函数模板实现泛型构造
成员函数模板可生成接受任意兼容类型的构造函数和赋值函数template<typename T> class SharedPtr { public:template<typename Y>explicit SharedPtr(Y* p); // 从任意类型指针构造template<typename Y>SharedPtr(const SharedPtr<Y>& r); // 兼容类型拷贝构造 };
-
添加类型转换约束
使用std::enable_if
和类型特征确保安全转换template<typename T> class SmartPtr {template<typename U, typename = std::enable_if_t<std::is_convertible_v<U*, T*>>>SmartPtr(const SmartPtr<U>&); };
-
显式声明默认函数
避免成员模板隐藏编译器生成的默认函数template<typename T> class SmartPtr { public:// 显式声明拷贝操作SmartPtr(const SmartPtr&); SmartPtr& operator=(const SmartPtr&);// 成员模板构造函数... };
实战:跨类型智能指针赋值
class Base { /*...*/ }; class Derived : public Base { /*...*/ };SmartPtr<Base> pBase(new Base); SmartPtr<Derived> pDerived(new Derived);// 使用成员模板实现兼容类型赋值 pBase = pDerived; // 正确:通过泛型赋值操作符// 错误示例:类型不兼容 SmartPtr<int> pInt(new int); // pBase = pInt; // 编译错误:类型约束阻止转换
类型特征约束进阶:
// 使用更精确的约束:派生关系 template<typename T> class SmartPtr {template<typename U, typename = std::enable_if_t<std::is_base_of_v<T, U> || std::is_convertible_v<U*, T*>>>SmartPtr(const SmartPtr<U>&); };
总结:成员函数模板允许类模板生成接受任意兼容类型的函数,是实现泛型拷贝构造和赋值操作的关键技术。通过添加编译期类型约束(如std::enable_if
和类型特征)确保转换安全,同时显式声明默认拷贝操作以避免被模板隐藏。这一技术广泛用于智能指针、迭代器等需要类型灵活性的场景,是编写高级模板代码的必备技能。