Effective C++ 條款19:設計class猶如設計type
核心思想:設計新的class時,應當像語言設計者設計內置類型一樣慎重,考慮對象的創建、銷毀、初始化、拷貝、類型轉換等所有方面。
?? 1. 類設計的關鍵問題域
對象生命周期管理:
class ResourceHandle {
public:// 構造和析構:資源如何獲取?如何釋放?ResourceHandle(const std::string& resId);~ResourceHandle(); // 需要釋放資源嗎?private:Resource* resource_;
};
值語義與行為:
class Rational {
public:// 拷貝操作:允許拷貝嗎?淺拷貝還是深拷貝?Rational(const Rational& other);Rational& operator=(const Rational& other);// 類型轉換:支持隱式轉換嗎?operator double() const; // 危險:可能非預期轉換
};
🚨 2. 解決方案:系統化設計方法
明確對象創建方式:
class Session {
public:// 靜態工廠方法:控制創建邏輯static Session createFromNetwork();static Session createFromFile(const std::string& path);// 禁用拷貝Session(const Session&) = delete;Session& operator=(const Session&) = delete;
private:Session(); // 私有構造
};
安全類型轉換接口:
class SafeRational {
public:// 顯式轉換函數(C++11)explicit operator double() const { return static_cast<double>(numerator)/denominator; }// 轉換運算符替代方案double toDouble() const { /* ... */ } // 更安全的顯式轉換
};
?? 3. 關鍵設計原則與決策
設計維度 | 關鍵問題 | 推薦實踐 |
---|---|---|
對象創建/銷毀 | 構造函數參數?析構函數必要性? | RAII模式管理資源 |
初始化/賦值區別 | 構造函數與賦值操作符行為是否一致? | 確保一致性 |
值傳遞方式 | pass-by-value是否高效? | 小對象傳值,大對象傳const引用 |
操作符重載 | 哪些操作符需要重載? | 僅重載符合直覺的操作符 |
類型轉換控制 | 是否允許隱式轉換? | 使用explicit 禁止非預期轉換 |
成員訪問權限 | 哪些成員公開?哪些需要保護? | 最小化public接口 |
繼承體系設計 | 是否作為基類?虛函數如何設計? | 明確聲明final 或override |
模板泛化可能性 | 是否應設計為類模板? | 評估未來需求 |
標準庫兼容性 | 是否滿足STL容器要求? | 提供必要的類型特征 |
成員函數設計規范:
class Polynomial {
public:// 常量成員函數:不修改對象狀態double evaluate(double x) const noexcept;// 異常安全保證void normalize() &; // 僅限左值對象調用// 引用限定符(C++11)void process() &&; // 僅限右值對象調用
};
繼承體系設計規范:
// 接口類設計
class Drawable {
public:virtual void draw() const = 0;virtual ~Drawable() = default;// 禁止拷貝(接口類通常不可拷貝)Drawable(const Drawable&) = delete;Drawable& operator=(const Drawable&) = delete;
};// 具體實現類
class Circle final : public Drawable {
public:void draw() const override; // 明確重寫// ... // 禁止進一步繼承(final)
};
💡 關鍵原則總結
- 生命周期全周期設計
- 構造/析構:資源獲取即初始化(RAII)
- 拷貝控制:明確
=default
/=delete
拷貝操作
- 類型行為一致性
- 操作符重載:行為需符合內置類型預期
- 類型轉換:優先使用
explicit
和命名轉換函數
- 接口最小化原則
- 成員函數:提供完備但最小的操作集合
- 訪問控制:嚴格限制
private
/protected
- 繼承體系明確性
- 基類:聲明虛析構函數,明確抽象接口
- 派生類:使用
final
/override
明確意圖
危險類設計示例:
class AutoPtr { // 已廢棄的auto_ptr問題 public:// 問題1:允許從臨時對象構造AutoPtr(AutoPtr& other); // 非const引用// 問題2:轉移所有權但不明確AutoPtr& operator=(AutoPtr& other);// 問題3:支持隱式指針轉換operator void*() const; // 可能導致誤用 };
安全重構方案:
// 解決方案:現代unique_ptr設計理念 template<typename T> class UniquePtr { public:// 明確所有權轉移語義UniquePtr(UniquePtr&& other) noexcept; // 移動構造UniquePtr& operator=(UniquePtr&& other) noexcept; // 移動賦值// 禁止拷貝UniquePtr(const UniquePtr&) = delete;UniquePtr& operator=(const UniquePtr&) = delete;// 顯式bool轉換(安全)explicit operator bool() const noexcept;// 明確資源釋放接口void reset() noexcept;T* release() noexcept; };