拷貝構造函數
通常在如下情況下會使用拷貝構造函數:
-
使用另一個同類型的對象來初始化新創建的對象。
-
復制對象把它作為參數傳遞給函數。
-
復制對象,並從函數返回這個對象。
拷貝構造函數的常見結構:
classname (const classname &obj) { // 構造函數的主體 }
值傳遞方式
#include <iostream> using namespace std; class A { public: A() { cout << "construct" << endl; } A(const A& a) // 拷貝構造函數 { cout << "copy construct" << endl; } ~A() { cout << "destruct" << endl; } }; A fun() // 全局函數 { return A(); } int main() { A a = fun(); return 0; }
類作為特殊的數據類型也可以通過值傳遞的方式進行對象的傳遞和拷貝,上述對象的值傳遞過程需要進行如下兩次對象拷貝:
-
fun()函數的局部對象【拷貝】到main()中的臨時對象1,作為返回值;
-
臨時對象1【拷貝】到對象a。
引用傳遞方式
通常類的數據量較大,采用傳值的方式傳遞和返回對象會調用拷貝構造函數進行對象的拷貝操作,會帶來大量內存空間的開辟和釋放,導致性能的損耗、降低效率。因此通常采用引用或指針的方式進行對象的傳遞,而實際上針對對象的操作通常都使用引用來進行。
對象引用作為返回值的一般框架:
A& fun(A& a) // 全局函數 { return a; } int main() { A a1; A& a2 = fun(a1); return 0; }
這里的fun函數采用對象的引用作為形參,這么做的目的是確保在返回對象的引用時,返回的對象不是臨時變量或局部變量,如果是返回的對象是臨時變量或局部變量,一旦該函數運行結束,局部變量或臨時變量很可能會被系統銷毀,導致編譯錯誤。即使系統沒有報錯,那也是因為g++編譯時會進行返回值優化,此時執行的流程是:
-
fun()函數的局部變量在被銷毀之前,系統調用【拷貝】構造函數創建臨時變量1;
-
a2直接【引用】該臨時變量1(避免了上述傳遞過程中的臨時變量1到變量a的拷貝操作)。
右值引用
除了返回對象引用的方式之外,還可以通過右值引用的方式減少拷貝構造函數的使用次數。
A fun() // 全局函數 { return A(); } int main() { A&& a = fun(); // 常量左值引用 return 0; }
右值引用綁定了右值,讓fun()返回的臨時對象的生命周期延長了(與a一樣長),其執行過程與引用傳遞大致相同,即只進行了由局部變量到臨時變量1的拷貝過程,避免了上述值傳遞過程中的臨時變量1到變量a的拷貝過程。
備注:
1、直接運行上述程序,可能有些流程打印不出來,原因:c++11 測試右值 臨時對象的構造 編譯器會自動優化導致有些流程未打印
需要添加-fno-elide-constructors標記
參考:
1、-fno-elide-constructors:https://blog.csdn.net/with_dream/article/details/85131151