標籤:運算 包含 ++ 資料 初始化 類型轉換 注意 分配 inline
C++中的預設函數與default和delete用法
一、 類中的預設函數
a.類中預設的成員函數
1.預設建構函式
2.預設解構函式
3.拷貝建構函式
4.拷貝賦值函數
5.移動建構函式
6.移動拷貝函數
b.類中自訂的操作符函數
1.operator
2.operator&
3.operator&&
4.operator*
5.operator->
6.operator->*
7.operator new
8.operator delete
同時C++規定,一旦程式員實現了這些函數的自訂版本,則編譯器不會再自動生產預設版本。注意只是不自動產生預設版本,當然還是可手動產生預設版本的。當我們自己定義了待參數的建構函式時,我們最好是聲明不帶參數的版本以完成無參的變數初始化,此時編譯是不會再自動提供預設的無參版本了。我們可以通過使用關鍵字default來控制預設建構函式的產生,顯式地指示編譯器產生該函數的預設版本。比如:
class MyClass
{
public:
MyClass()=default; //同時提供預設版本和帶參版本,類型是POD的
MyClass(int i):data(i){}
private:
int data;
};
有些時候我們希望限制預設函數的產生。典型的是禁止使用拷貝建構函式,以往的做法是將拷貝建構函式聲明為private的並不提供實現,這樣當拷貝構造對象時編譯不能通過,C++11則使用delete關鍵字顯式指示編譯器不產生函數的預設版本。比如:
class MyClass
{
public:
MyClass()=default;
MyClass(const MyClass& )=delete;
......
}
當然,一旦函數被delete過了,那麼重載該函數也是非法的,該函數我們習慣上稱為刪除函數。
二、default和delete的其他用途
上面我們已經看到在類中我們可用default和delete修飾成員函數,使之成為預設函數或者刪除函數,在類的外面,default可以在類定義之外修飾成員函數,比如:
class MyClass
{
public:
MyClass()=default;
MyClass() &operator=(const MyClass& );
);
//在類的定義外用default來指明預設函數版本
inline MyClass& MyClass::operator=(const MyClass& )=default;
還可以MyClass& MyClass::operator=(const MyClass& )=default;但是這種類外定義合成的成員就不是內嵌函式。
與=default 不同,=delete必須出現在函數的第一次聲明中。因為一個預設的成員隻影響為這個成員產生的程式碼,因此=default 直到編譯產生代碼時才需要,而編譯器必須早早知道一個函數是否是刪除的,以便禁止試圖使用它的操作。
一般情況下,解構函式不能定義為刪除的,因為如果解構函式被刪除,就無法刪除此類型的對象了。對於一個刪除了解構函式的類型,編譯器將不允許定義該類型的變數或建立該類型的臨時對象,而且如果一個類有某個成員的類型刪除了解構函式,也不能定義該類的變數或臨時對象,因為一個成員的解構函式是刪除的,則該成員無法銷毀,包含它的類也就沒法銷毀。雖然對於刪除了解構函式的類型,我們不能定義這種類型的變數或成員但可以動態分配這種類型的對象,比如:
Struct NoDtor{
NoDtor()=default;
~NoDtor()=default;
};
NoDtor *P=new NoDtor();//正確,但是我能delete P
但是有時解構函式也是可以delete的,這樣做的目的是我們在指定記憶體位置進行記憶體配置時並不需要解構函式來完成對象層級的清理,這時我們可顯示刪除解構函式來限制自訂類型在棧上或者靜態構造。
關於delete的顯式刪除,並非局限於成員函數,比如:
void Func(int i){};
void Func(char c)=delete; //顯式刪除char版本
int main()
{
Func(3);
Func(‘c’); //無法編譯通過
return 0;
}
這裡因為Func的char版本已經被刪除,故Func(‘c‘)會編譯失敗。由此我們也知default是只局限作用於類的部分成員函數的。於是我們還可用delete來避免不必要的隱式資料類型轉換。比如:
class MyClass
{
public:
MyClass(int i){};
MyClsss(char c)=delete; //刪除char版本的建構函式
};
void Fun(MyClass m){}
int main()
{
Func(3);
Func(‘a‘); //編譯不能通過
MyClass m1(3);
MyClass m2(‘a‘); //編譯不能通過
}
這是因為char版本的建構函式被刪除後,試圖從char構造MyClass對象的方式是不允許的了。但去掉這句的函數刪除後,編譯器會隱式的將a轉換為整型使得編譯通過,調用的是整型建構函式,這可能並不是你所想要的。
但是如果這樣:
class MyClass
{
public:
MyClass(int i){};
explicit MyClsss(char c)=delete; //刪除explicit的char版本的建構函式
};
void Fun(MyClass m){}
int main()
{
Func(3);
Func(‘a‘); //編譯可通過
MyClass m1(3);
MyClass m2(‘a‘); //編譯不能通過
}
將建構函式explicit後,建構函式一樣的還是不能發生char的構造,因為char構造版本被刪除了,但在Func的調用中,編譯器會嘗試將c轉換為int,即Func(\\a‘)會調用一次MyClass(int )構造,順利通過編譯。於是我們不提倡explicit和delete混用。對與普通函數delete也有類型的效果。
delete的用法還包括刪除operator new操作符,編碼在堆上分配該類的對象
如:void* operator new(std::size_t)=delete;
合成的拷貝控製成員可能是刪除的,如果一個類有資料成員不能預設構造、拷貝、複製或銷毀,則對應的成員函數將被定義為刪除的。因此:
如果類的某個成員的解構函式是刪除的或不可訪問的,那麼類的合成解構函式被定義為刪除的。
如果類的某個成員的拷貝建構函式是刪除的或不可訪問的,則類的合成拷貝建構函式也被定義為刪除的。
如果類的某個成員的拷貝賦值運算子是刪除的或不可訪問的,或是類有一個const的或引用成員,則類的合成拷貝賦值運算子被定義為刪除的。
如果類的某個成員的解構函式是刪除的或不可訪問的,或是類有一個引用成員,它沒有類內初始化器,或是類有一個const成員,它沒有類內初始化器且其類型未顯示定義預設建構函式,則該類的預設建構函式被定義為刪除的
C++ default 和delete的新用法