首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> C語言 >> C++ >> 關於C++

Effective Modern C++ 條款 17:特殊成員函數生成規則全解析

     編輯:關於C++

C++11 引入移動語義後,特殊成員函數從「四大」變「六大」:默認構造、析構、拷貝構造、拷貝賦值、移動構造、移動賦值。編譯器自動生成規則發生重大變化,掌握這些規則對避免隱式生成錯誤函數、性能退化、資源管理缺陷至關重要。

一、六大特殊成員函數簽名

class Widget {
public:
    Widget();                         // 默認構造
    ~Widget();                        // 析構
    Widget(const Widget&);            // 拷貝構造
    Widget& operator=(const Widget&); // 拷貝賦值
    Widget(Widget&&);                 // 移動構造 (C++11)
    Widget& operator=(Widget&&);      // 移動賦值 (C++11)
};

二、生成規則總表

函數生成條件被刪除條件運行期行為
默認構造 類無任何用戶聲明構造函數 — 成員逐一默認初始化
析構 無用戶聲明析構 — 成員逐一銷毀(逆序);基類虛析構則虛化
拷貝構造 無用戶聲明拷貝構造 類聲明了移動操作 非靜態成員逐一拷貝構造
拷貝賦值 無用戶聲明拷貝賦值 類聲明了移動操作 非靜態成員逐一拷貝賦值
移動構造 無用戶聲明拷貝操作、移動操作、析構 — 非靜態成員逐一移動構造(不支持移動則退化為拷貝)
移動賦值 無用戶聲明拷貝操作、移動操作、析構 — 非靜態成員逐一移動賦值

三、核心規則口訣

  1. 移動操作「孤兒」規則:類中只要聲明了 拷貝構造、拷貝賦值、析構、移動構造、移動賦值 任意一個,編譯器就不再生成移動操作。
  2. 拷貝操作「獨立」規則:聲明拷貝構造不阻止編譯器生成拷貝賦值,反之亦然(C++98 兼容)。但聲明移動操作會刪除拷貝操作(= delete)。
  3. 析構函數「抑制移動」:用戶聲明析構 → 編譯器不生成移動操作(為兼容 C++98 仍生成拷貝,但不推薦依賴)。
  4. = default 顯式挽救:若需編譯器生成版本,在類中聲明並 = default,可選擇在實現文件定義(配合 Pimpl 見條款 22)。

四、經典陷阱:添加日誌導致性能崩塌

class StringTable {
    std::map<int, std::string> _data;
public:
    StringTable() { Log("create"); }
    ~StringTable() { Log("destroy"); } // 用戶聲明析構
    // 無聲明拷貝/移動
};

// 後果:編譯器不生成移動操作!
// 調用 std::move(st) 實際執行拷貝構造 → 深拷貝 map → 性能降級數量級

修正:顯式 = default 移動/拷貝操作。

StringTable(StringTable&&) = default;
StringTable& operator=(StringTable&&) = default;
StringTable(const StringTable&) = default;
StringTable& operator=(const StringTable&) = default;

五、多態基類最佳實踐

class Base {
public:
    virtual ~Base() = default; // 必須虛析構
    Base(Base&&) = default;
    Base& operator=(Base&&) = default;
    Base(const Base&) = default;
    Base& operator=(const Base&) = default;
};
  • 虛析構強制用戶聲明 → 必須 = default 移動/拷貝以保留值語義。
  • 派生類自動繼承正確行為。

六、成員函數模板不抑制生成

class Widget {
    template<typename T> Widget(const T&); // 通用構造模板
    template<typename T> Widget& operator=(const T&);
};
// 編譯器仍會生成拷貝構造/賦值(當 T=Widget 時模板不參與重載決策)
// 見條款 26 討論完美轉發構造的陷阱

七、總結:必記四點

  1. 特殊成員函數:默認構造、析構、拷貝構造、拷貝賦值、移動構造、移動賦值。
  2. 移動操作僅在 類無聲明拷貝操作、移動操作、析構時生成。
  3. 聲明移動操作 → 拷貝操作被刪除;聲明析構 → 移動操作不生成(拷貝仍生成但不推薦)。
  4. 成員函數模板從不抑制特殊成員函數生成。

實戰清單:定義類時,問自己三個問題:
1. 需要移動語義嗎?→ 不要聲明析構/拷貝/移動,或全部 = default。
2. 需要拷貝語義嗎?→ 不要聲明移動操作。
3. 是多態基類嗎?→ virtual ~Base() = default; + 四個 = default。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved