C++11 引入移動語義後,特殊成員函數從「四大」變「六大」:默認構造、析構、拷貝構造、拷貝賦值、移動構造、移動賦值。編譯器自動生成規則發生重大變化,掌握這些規則對避免隱式生成錯誤函數、性能退化、資源管理缺陷至關重要。
class Widget {
public:
Widget(); // 默認構造
~Widget(); // 析構
Widget(const Widget&); // 拷貝構造
Widget& operator=(const Widget&); // 拷貝賦值
Widget(Widget&&); // 移動構造 (C++11)
Widget& operator=(Widget&&); // 移動賦值 (C++11)
};
| 函數 | 生成條件 | 被刪除條件 | 運行期行為 |
|---|---|---|---|
| 默認構造 | 類無任何用戶聲明構造函數 | — | 成員逐一默認初始化 |
| 析構 | 無用戶聲明析構 | — | 成員逐一銷毀(逆序);基類虛析構則虛化 |
| 拷貝構造 | 無用戶聲明拷貝構造 | 類聲明了移動操作 | 非靜態成員逐一拷貝構造 |
| 拷貝賦值 | 無用戶聲明拷貝賦值 | 類聲明了移動操作 | 非靜態成員逐一拷貝賦值 |
| 移動構造 | 無用戶聲明拷貝操作、移動操作、析構 | — | 非靜態成員逐一移動構造(不支持移動則退化為拷貝) |
| 移動賦值 | 無用戶聲明拷貝操作、移動操作、析構 | — | 非靜態成員逐一移動賦值 |
= 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. 需要移動語義嗎?→ 不要聲明析構/拷貝/移動,或全部= default。
2. 需要拷貝語義嗎?→ 不要聲明移動操作。
3. 是多態基類嗎?→virtual ~Base() = default;+ 四個= default。