在 C++ 中,若類別未顯式宣告,編譯器會隱式生成六個特殊成員函數(C++98 四個,C++11 新增兩個移動語義函數):
這些函數預設為 `public` 且 `inline`,實作為成員逐一拷貝/移動。但有時我們需要禁止某些操作(如不可複製的資源管理類別),C++98 與 C++11 提供了截然不同的解決方案。
傳統做法是將拷貝建構函式與拷貝指派運算子宣告為 `private` 且不提供定義:
class NonCopyable {
private:
NonCopyable(const NonCopyable&); // 僅宣告,不定義
NonCopyable& operator=(const NonCopyable&); // 僅宣告,不定義
};
若程式碼嘗試複製,會在連結階段報錯(link-time error)。為了將錯誤提前到編譯期,常引入基類 `Uncopyable` 並採用私有繼承:
class Uncopyable {
protected:
Uncopyable() {}
~Uncopyable() {}
private:
Uncopyable(const Uncopyable&);
Uncopyable& operator=(const Uncopyable&);
};
class Leaf : private Uncopyable { /* ... */ };
此技巧雖有效,但語法繁瑣,且錯誤訊息往往晦澀難懂。
C++11 引入刪除函數語法,只需在宣告後加上 `= delete` 即可明確禁用:
class Leaf {
public:
Leaf() = default; // 顯式要求編譯器生成預設版本
~Leaf() = default;
Leaf(const Leaf&) = delete; // 禁用拷貝建構
Leaf& operator=(const Leaf&) = delete; // 禁用拷貝指派
};
優勢顯而易見:
C++11 允許對任意函數使用 `= delete`,包括非成員函數、函數模板特化等,極大擴展了用途。
例如 `isLucky` 函數僅接受 `int`,拒絕 `char`、`bool`、`double` 等隱式轉換:
bool isLucky(int number); // 唯一允許的多載
bool isLucky(char) = delete; // 拒絕字元
bool isLucky(bool) = delete; // 拒絕布林
bool isLucky(double) = delete; // 拒絕浮點數
// 調用示例
isLucky(7); // OK
isLucky('a'); // 編譯錯誤:call to deleted function
isLucky(true); // 編譯錯誤
isLucky(3.14); // 編譯錯誤
這比 `static_assert` 或執行期檢查更優雅,直接在編譯期阻擋不當類型。
C++98 中,若想禁用類別模板的特定特化(如 `processPointer
class Widget {
public:
template <typename T>
void processPointer(T* ptr) { /* ... */ }
};
// 在命名空間作用域刪除 void* 特化
template <>
void Widget::processPointer<void>(void*) = delete;
此時調用 `processPointer(static_cast
`= delete` 是 C++11 強化類型安全、簡化資源管理類別設計的利器,現代 C++ 開發應熟練掌握並廣泛應用。