簡介:
本文將介紹 C++11 標準的兩個新特性:defaulted 和 deleted 函數。對於 defaulted 函數,編譯器會為其自動產生預設的函數定義體,從而獲得更高的代碼執行效率,也可免除程式員手動定義該函數的工作量。對於 deleted 函數, 編譯器會對其禁用,從而避免某些非法的函數調用或者類型轉換,從而提高代碼的安全性。本文將通過程式碼範例詳細闡述 defaulted 和 deleted 函數的用法及益處。
Defaulted 函數
背景問題
C++ 的類有四類特殊成員函數,它們分別是:預設建構函式、解構函式、拷貝建構函式以及拷貝賦值運算子。這些類的特殊成員函數負責建立、初始化、銷毀,或者拷貝 類的對象。如果程式員沒有顯式地為一個類定義某個特殊成員函數,而又需要用到該特殊成員函數時,則編譯器會隱式的為這個類產生一個預設的特殊成員函數。例 如:
清單 1
class X{ private: int a; }; X x; |
在清單 1 中,程式員並沒有定義類X的預設建構函式,但是在建立類X的對象x的時候,又需要用到類X的預設建構函式,此時,編譯器會隱式的為類X產生一個預設建構函式。該自動產生的預設建構函式沒有參數,包含一個空的函數體,即X::X(){ }。雖然自動產生的預設建構函式僅有一個空函數體,但是它仍可用來成功建立類X的對象x,清單 1 也可以編譯通過。
但是,如果程式員為類 X 顯式的自訂了非預設建構函式,卻沒有定義預設建構函式的時候,清單 2 將會出現編譯錯誤:
清單 2
class X{ public: X(int i){ a = i; } private: int a; }; X x; // 錯誤 , 預設建構函式 X::X() 不存在 |
清單 2 編譯出錯的原因在於類X已經有了使用者自訂的建構函式,所以編譯器將不再會為它隱式的產生預設建構函式。如果需要用到預設建構函式來建立類的對象時,程式員必須自己顯式的定義預設建構函式。例如:
清單 3
class X{ public: X(){}; // 手動定義預設建構函式 X(int i){ a = i; } private: int a; }; X x; // 正確,預設建構函式 X::X() 存在 |
從清單 3 可以看出,原本期望編譯器自動產生的預設建構函式需要程式員手動編寫了,即程式員的工作量加大了。此外,手動編寫的預設建構函式的代碼執行效率比編譯器自 動產生的預設建構函式低。類的其它幾類特殊成員函數也和預設建構函式一樣,當存在使用者自訂的特殊成員函數時,編譯器將不會隱式的自動產生預設特殊成員函 數,而需要程式員手動編寫,加大了程式員的工作量。類似的,手動編寫的特殊成員函數的代碼執行效率比編譯器自動產生的特殊成員函數低。
Defaulted 函數的提出
為瞭解決如清單 3 所示的兩個問題:1. 減輕程式員的編程工作量;2. 獲得編譯器自動產生的預設特殊成員函數的高的代碼執行效率,C++11 標準引入了一個新特性:defaulted 函數。程式員只需在函式宣告後加上“=default;”,就可將該函式宣告為 defaulted 函數,編譯器將為顯式聲明的 defaulted 函數自動產生函數體。例如:
清單 4
class X{ public: X()= default; X(int i){ a = i; } private: int a; }; X x; |
在清單 4 中,編譯器會自動產生預設建構函式X::X(){},該函數可以比使用者自己定義的預設建構函式獲得更高的代碼效率。
Defaulted 函數定義文法
Defaulted 函數是 C++11 標準引入的函數定義新文法,defaulted 函數定義的文法 1 所示:
圖 1. Defaulted 函數定義文法圖
Defaulted 函數的用法及樣本
Defaulted 函數特性僅適用於類的特殊成員函數,且該特殊成員函數沒有預設參數。例如:
清單 5
class X { public: int f() = default; // 錯誤 , 函數 f() 非類 X 的特殊成員函數 X(int) = default; // 錯誤 , 建構函式 X(int, int) 非 X 的特殊成員函數 X(int = 1) = default; // 錯誤 , 預設建構函式 X(int=1) 含有預設參數 }; |
Defaulted 函數既可以在類體裡(inline)定義,也可以在類體外(out-of-line)定義。例如:
清單 6
class X{ public: X() = default; //Inline defaulted 預設建構函式 X(const X&); X& operator = (const X&); ~X() = default; //Inline defaulted 解構函式 }; X::X(const X&) = default; //Out-of-line defaulted 拷貝建構函式 X& X::operator = (const X&) = default; //Out-of-line defaulted // 拷貝賦值操作符 |
在 C++ 代碼編譯過程中,如果程式員沒有為類X定義解構函式,但是在銷毀類X對象的時候又需要調用類X的解構函式時,編譯器會自動隱式的為該類產生一個解構函式。該自動產生的解構函式沒有參數,包含一個空的函數體,即X::~X(){ }。例如:
清單 7
class X { private: int x; }; class Y: public X { private: int y; }; int main(){ X* x = new Y; delete x; } |
在清單 7 中,程式員沒有為基類 X 和衍生類別 Y 定義解構函式,當在主函數內 delete 基類指標 x 的時候,需要調用基類的解構函式。於是,編譯器會隱式自動的為類 X 產生一個解構函式,從而可以成功的銷毀 x 指向的衍生類別對象中的基類子物件(即 int 型成員變數 x)。
但是,這段代碼存在記憶體泄露的問題,當利用delete語句刪除指向衍生類別對象的指標x時,系統調用的是基類的解構函式,而非衍生類別Y類的解構函式,因此,編譯器無法析構衍生類別的int型成員變數 y。
因此,一般情況下我們需要將基類的解構函式定義為虛函數,當利用 delete 語句刪除指向衍生類別對象的基類指標時,系統會調用相應的衍生類別的解構函式(實現多態性),從而避免記憶體泄露。但是編譯器隱式自動產生的解構函式都是非虛函數,這就需要由程式員手動的為基類X定義虛解構函式,例如:
清單 8
class X { public: virtual ~X(){}; // 手動定義虛解構函式 private: int x; }; class Y: public X { private: int y; }; int main(){ X* x = new Y; delete x; } |
在清單 8 中,由於程式員手動為基類X定義了虛解構函式,當利用delete語句刪除指向衍生類別對象的基類指標x時,系統會調用相應的衍生類別Y的解構函式(由編譯器隱式自動產生)以及基類X的解構函式,從而將衍生類別對象完整的銷毀,可以避免記憶體泄露。
但是,在清單 8 中,程式員需要手動的編寫基類的虛構函數的定義(哪怕函數體是空的),增加了程式員的編程工作量。更值得一提的是,手動定義的解構函式的代碼執行效率要低於編譯器自動產生的解構函式。
為瞭解決上述問題,我們可以將基類的虛解構函式聲明為 defaulted 函數,這樣就可以顯式的指定編譯器為該函數自動產生函數體。例如:
清單 9
class X { public: virtual ~X()= defaulted; // 編譯器自動產生 defaulted 函數定義體 private: int x; }; class Y: public X { private: int y; }; int main(){ X* x = new Y; delete x; |
}
在清單 9 中,編譯器會自動產生虛解構函式virtual X::X(){},該函數比使用者自己定義的虛解構函式具有更高的代碼執行效率。
Deleted 函數
背景問題
對於 C++ 的類,如果程式員沒有為其定義特殊成員函數,那麼在需要用到某個特殊成員函數的時候,編譯器會隱式的自動產生一個預設的特殊成員函數,比如拷貝建構函式,或者拷貝賦值操作符。例如:
清單 10
class X{ public: X(); }; int main(){ X x1; X x2=x1; // 正確,調用編譯器隱式產生的預設拷貝建構函式 X x3; x3=x1; // 正確,調用編譯器隱式產生的預設拷貝賦值操作符 } |
在清單 10 中,程式員不需要自己手動編寫拷貝建構函式以及拷貝賦值操作符,依靠編譯器自動產生的預設拷貝建構函式以及拷貝賦值操作符就可以實作類別對象的拷貝和賦值。 這在某些情況下是非常方便省事的,但是在某些情況下,假設我們不允許發生類對象之間的拷貝和賦值,可是又無法阻止編譯器隱式自動產生預設的拷貝建構函式以 及拷貝賦值操作符,那這就成為一個問題了。
Deleted 函數的提出
為了能夠讓程式員顯式的禁用某個函數,C++11 標準引入了一個新特性:deleted 函數。程式員只需在函式宣告後加上“=delete;”,就可將該函數禁用。例如,我們可以將類X的拷貝建構函式以及拷貝賦值操作符聲明為 deleted 函數,就可以禁止類X對象之間的拷貝和賦值。
清單 11
class X{ public: X(); X(const X&) = delete; // 聲明拷貝建構函式為 deleted 函數 X& operator = (const X &) = delete; // 聲明拷貝賦值操作符為 deleted 函數 }; int main(){ X x1; X x2=x1; // 錯誤,拷貝建構函式被禁用 X x3; x3=x1; // 錯誤,拷貝賦值操作符被禁用 } |
在清單 11 中,雖然只顯式的禁用了一個拷貝建構函式和一個拷貝賦值操作符,但是由於編譯器檢測到類X存在使用者自訂的拷貝建構函式和拷貝賦值操作符的聲明,所以不會再隱式的產生其它參數類型的拷貝建構函式或拷貝賦值操作符,也就相當於類X沒有任何拷貝建構函式和拷貝賦值操作符,所以對象間的拷貝和賦值被完全禁止了。
Deleted 函數定義文法
Deleted 函數是 C++11 標準引入的函數定義新文法,deleted 函數定義的文法 2 所示:
圖 2. Deleted 函數定義文法圖
Deleted 函數的用法及樣本
Deleted 函數特性還可用于禁用類的某些轉換建構函式,從而避免不期望的類型轉換。在清單 12 中,假設類X只支援參數為雙精確度浮點數 double 類型的轉換建構函式,而不支援參數為整數 int 類型的轉換建構函式,則可以將參數為 int 類型的轉換建構函式聲明為 deleted 函數。
清單 12
class X{ public: X(double); X(int) = delete; }; int main(){ X x1(1.2); X x2(2); // 錯誤,參數為整數 int 類型的轉換建構函式被禁用 } |
Deleted 函數特性還可以用來禁用某些使用者自訂的類的new操作符,從而避免在自由儲存區建立類的對象。例如:
清單 13
#include <cstddef> using namespace std; class X{ public: void *operator new(size_t) = delete; void *operator new[](size_t) = delete; }; int main(){ X *pa = new X; // 錯誤,new 操作符被禁用 X *pb = new X[10]; // 錯誤,new[] 操作符被禁用 } |
必須在函數第一次聲明的時候將其聲明為 deleted 函數,否則編譯器會報錯。即對於類的成員函數而言,deleted 函數必須在類體裡(inline)定義,而不能在類體外(out-of-line)定義。例如:
清單 14
class X { public: X(const X&); }; X::X(const X&) = delete; // 錯誤,deleted 函數必須在函數第一次聲明處聲明 |
雖然 defaulted 函數特性規定了只有類的特殊成員函數才能被聲明為 defaulted 函數,但是 deleted 函數特性並沒有此限制。非類的成員函數,即普通函數也可以被聲明為 deleted 函數。例如:
清單 15
int add (int,int)=delete; int main(){ int a, b; add(a,b); // 錯誤,函數 add(int, int) 被禁用 } |
值得一提的是,在清單 15 中,雖然add(int, int)函數被禁用了,但是禁用的僅是函數的定義,即該函數不能被調用。但是函數標示符add仍是有效,在名字尋找和函數重載解析時仍會尋找到該函數標示符。如果編譯器在解析重載函數時,解析結果為 deleted 函數,則會出現編譯錯誤。例如:
清單 16
#include <iostream> using namespace std; int add(int,int) = delete; double add(double a,double b){ return a+b; } int main(){ cout << add(1,3) << endl; // 錯誤,調用了 deleted 函數 add(int, int) cout << add(1.2,1.3) << endl; return 0; } |
結束語
本文詳細介紹了 C++11 新特性 defaulted 和 deleted 函數。該特性巧妙地對 C++ 已有的關鍵字 default 和 delete 的文法進行了擴充,引入了兩種新的函數定義方式:在函式宣告後加 =default 和 =delete。通過將類的特殊成員函式宣告為 defaulted 函數,可以顯式指定編譯器為該函數自動產生預設函數體。通過將函式宣告為 deleted 函數,可以禁用某些不期望的轉換或者操作符。Defaulted 和 deleted 函數特性文法簡單,功能實用,是對 C++
標準的一個非常有價值的擴充。
原文出處:IBM developerWorks