目錄
(1)都長什麼樣?有何用處?
(2)何時會被用到?
(1)都長什麼樣?有何用處?
a. 拷貝構造函數,要求第一個形參是類類型的(const)左值引用,若有其他形參(通常沒有),則必須有預設值。形式如下:
class A
{
private:
int i;
char *ptr;
static string str;
};
A(const A& temp, int useless = 0):i(temp.i), ptr(new char[strlen(temp.ptr) + 1]) //拷貝構造函數,拷貝資源
{
strcpy(ptr,temp.ptr);
}
可見,拷貝構造函數用于,将其他對象的非static資料成員及動态資源逐個拷貝到正在建立的對象中。構造結束後兩個對象的資料成員和動态資源互相獨立(其實可以共享)。
b. 移動構造函數對形參的要求與拷貝構造函數類似,唯一差別是第一個形參須是類類型的右值引用。形式如下:
A(A&& temp, int useless = 0):i(temp.i), ptr(temp.ptr) //移動構造函數,接管而非拷貝資源
{
temp.ptr = NULL; //確定銷毀安全
}
正如上述代碼所示,移動構造函數實作了,其他對象的非static資料成員及動态資源被正在建立的對象所接管。資源被接管的對象通常不會再被使用,而是被銷毀,是以為了確定此對象銷毀後不影響被接管的動态資源,管理動态資源的資料成員應不再指向此資源。
c. 拷貝指派運算符,通常接受右側運算對象的(const)左值引用,并傳回左側運算對象的左值引用。示例如下:
A& operator=(const A& temp)
{
if(this != &temp) //檢查自指派
{
free(ptr);
ptr = new char[strlen(temp.ptr) + 1]; //拷貝資源
strcpy(ptr, temp.ptr);
i = temp.i;
}
return *this;
}
可見,拷貝指派運算符用于,釋放左側運算對象管理的動态資源,并把右側運算對象的非static資料成員及動态資源拷貝給左側運算對象。為了順利完成資源拷貝,檢查自指派情況是必要的。
d. 移動指派運算符與拷貝指派運算符有一點類似,即通常傳回左側運算對象的左值引用,但接受的往往是右側運算對象的右值引用。示例如下:
A& operator=(A&& temp)
{
if(this != &temp) //檢查自指派
{
free(ptr);
ptr = temp.ptr; //接管資源而非拷貝資源
temp.ptr = NULL;
i = temp.i;
}
return *this;
}
移動指派運算符會先釋放左側運算對象管理的動态資源。接着,與移動構造函數類似,實作了左側運算對象對右側運算對象的非static資料成員及動态資源的接管。同樣地, 為了順利完成資源接管,先要檢查自指派情況。為了確定右側運算對象銷毀後不影響被接管的動态資源,管理動态資源的資料成員應不再指向此資源。
e. 析構函數形式簡單,形參也不需要。如下:
~A()
{
free(ptr);
}
析構函數的函數體,負責銷毀對象管理的動态資源。而對象所擁有的非static資料成員的銷毀不需要程式員操心,它們是在析構函數體外隐含的析構階段被自動銷毀的。
(2)何時會被用到?
a. 由于拷貝/移動構造函數本身屬于構造函數,是以當定義對象時傳入的參數是左值/右值,則拷貝/移動構造函數相應被調用;
b. 拷貝/移動構造函數與對象拷貝初始化這一概念密切相關。先解釋何為對象拷貝初始化,以及與其相近的概念——直接初始化:
拷貝初始化和直接初始化都是初始化對象的方式:
---- 直接初始化意味着,定義對象時直接傳入某種形式的參數,引發相應構造函數的調用;
---- 拷貝初始化是将指派号右側的運算對象拷貝到正在建立的對象中,右側運算對象不一定是類型正确的對象,還可能是對象中的某一資料成員,憑借隐式類類型轉換可變為類型正确的對象。
具體示例如下:
string s1(10,'c'); //直接初始化
string s2(s1); //直接初始化
string s3 = s2; //拷貝初始化
string s4 = "hello"; //拷貝初始化
當拷貝初始化發生時,被拷貝的對象是左值/右值,則拷貝/移動構造函數相應被調用。拷貝初始化會在以下情況中出現:
---- 定義對象時使用=;
---- 将對象作為實參傳遞給非引用類型的實參;
---- 從一個傳回類型為非引用類型的函數傳回對象;
---- 用花括号清單初始化一個數組中的元素或聚合類中的成員;
---- 某些容器在建立對象時;
前三種情況顧名思義,不贅述,後兩種情況示例如下:
string s1("hello"), s2("world"), s3("everyone");
string s[] = { s1, s2, s3 }; //數組初始化
struct one
{
int i;
string s;
};
one temp = {5, s1}; //初始化聚合類對象
vector<string> vr;
vr.push_back(s1); //容器建立對象
以上五種情況發生時,拷貝/移動構造函數相應被調用。
c. 拷貝/移動指派運算符在指派發生時被調用,指派号右側的運算對象是左值/右值,則拷貝/指派運算符相應被調用。示例如下:
string s1("hello"), s2("nothing");
s1 = s2; // =右側是左值,拷貝指派運算符被調用
s1 = std::move(s2); // =右側是右值,移動指派運算符被調用
d. 析構函數在對象被銷毀時調用,而對象在以下情況中被銷毀:
---- 對象在指令執行超出作用域後被自動銷毀;
---- 臨時對象在完成任務後被自動銷毀;
---- 動态配置設定的對象被手動銷毀(調用delete)。