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
和類型特征)確保轉換安全,同時顯式聲明默認拷貝操作以避免被模板隱藏。這一技術廣泛用于智能指針、迭代器等需要類型靈活性的場景,是編寫高級模板代碼的必備技能。