《深入探索C++對(duì)象模型》筆記 Chapter2 構(gòu)造函數(shù)
《深入探索C++對(duì)象模型》筆記 Chapter3 成員變量
《深入探索C++對(duì)象模型》筆記 Chapter4 成員函數(shù)
第2章 構(gòu)造函數(shù)
2.1 默認(rèn)構(gòu)造函數(shù)
之前以為沒(méi)有聲明構(gòu)造函數(shù),編譯器就會(huì)自動(dòng)生成一個(gè)默認(rèn)構(gòu)造函數(shù)矢棚,然而事實(shí)上并不是這樣。默認(rèn)構(gòu)造函數(shù)分為 trival 和 nontrival ,對(duì)于無(wú)關(guān)緊要的默認(rèn)構(gòu)造函數(shù)属愤,編譯器不會(huì)生成动分。(這里多說(shuō)一句必逆,一個(gè)類的一些構(gòu)造函數(shù)以及析構(gòu)函數(shù)是否 trival 等特性,可以用 type_traits 提取出來(lái)乌昔,在模板編程中十分有用,比如刪除一個(gè)區(qū)間的元素壤追,如果析構(gòu)函數(shù)是 trival 磕道,那么就不需要一個(gè)個(gè)去調(diào)用析構(gòu)函數(shù)。以上內(nèi)容《STL源碼剖析》有詳細(xì)解釋)行冰。
nontrival 默認(rèn)構(gòu)造函數(shù)又分為以下幾種情況:
- 成員變量有默認(rèn)構(gòu)造函數(shù)
即使用戶自己實(shí)現(xiàn)了構(gòu)造函數(shù)溺蕉,也要擴(kuò)張構(gòu)造函數(shù),在用戶代碼執(zhí)行之前悼做,調(diào)用必需的默認(rèn)構(gòu)造函數(shù)疯特。 - 繼承的基類有默認(rèn)構(gòu)造函數(shù)
- 聲明了虛函數(shù)
在默認(rèn)構(gòu)造函數(shù)中初始化 vptr - 虛繼承
2.2 拷貝構(gòu)造函數(shù)
拷貝分為兩種,memberwise copy
和 bitwise copy
贿堰,前者是深拷貝辙芍,會(huì)遞歸拷貝成員變量,后者是淺拷貝羹与,將內(nèi)存中的每個(gè)字節(jié)拷貝過(guò)去故硅。
諸如A a1;A a2=a1;
這樣的代碼,即使A沒(méi)有聲明拷貝構(gòu)造函數(shù)纵搁,編譯器也可以完成上述的行為吃衅,依賴的就是memberwise copy
和 bitwise copy
。
同默認(rèn)構(gòu)造函數(shù)一樣腾誉,并不是沒(méi)有聲明拷貝構(gòu)造函數(shù)而編譯器需要時(shí)就會(huì)生成一個(gè)徘层,只有判斷 nontrival 才會(huì)生成峻呕。而是否 nontrival 取決于一個(gè)類是不是符合 bitwise copy 語(yǔ)義。判斷方式和2.1列的幾點(diǎn)類似趣效。
2.3節(jié)有一句話說(shuō)的很好瘦癌,判斷是否 trival 關(guān)鍵在于 “class 是否含有編譯器內(nèi)部產(chǎn)生的 member”。例如有虛函數(shù)跷敬,編譯器會(huì)給class 添加一個(gè) vptr 成員讯私,此時(shí)默認(rèn)構(gòu)造函數(shù)和拷貝構(gòu)造函數(shù)都是 nontrival 了,因?yàn)榫幾g器要做 vptr 的設(shè)置和修改西傀。
以及斤寇,如果成員都是POD類型, 那么沒(méi)有必要自己聲明拷貝構(gòu)造函數(shù)拥褂,因?yàn)榫幾g器做的已經(jīng)是最優(yōu)了娘锁。
2.3 NRV優(yōu)化
Named Return Value 優(yōu)化就是說(shuō)一個(gè)函數(shù)生成并返回一個(gè)臨時(shí)對(duì)象,需要調(diào)用構(gòu)造函數(shù)饺鹃,拷貝構(gòu)造函數(shù)莫秆,臨時(shí)對(duì)象的析構(gòu)函數(shù),這個(gè)過(guò)程效率很低下尤慰,編譯器可以做優(yōu)化馏锡。步驟如下:
- 函數(shù)添加一個(gè)參數(shù),為欲返回對(duì)象的引用
- 在調(diào)用函數(shù)前伟端,先創(chuàng)建該對(duì)象
- 進(jìn)入函數(shù)后杯道,就可以直接對(duì)這個(gè)對(duì)象的引用進(jìn)行操作
- 返回值就可以為空了
于是 A a = fun();
相當(dāng)于A a; fun(a);
只有一次構(gòu)造函數(shù)的消耗≡痱穑可以寫(xiě)個(gè)demo驗(yàn)證一下:
#include <iostream>
using namespace std;
class B{
public:
B(){
cout<< "this is constructor without parameter of B" <<endl;
}
B(int num):k(num){
cout<< "this is constructor with parameter of B" <<endl;
}
B(const B& b){
k=b.k;
cout<< "this is copy constructor of B"<<endl;
}
B& operator=(const B& b){
k=b.k;
cout << "this is operator= of B" <<endl;
return *this;
}
~B(){
cout<< "this is destroyor of B"<<endl;
}
private:
int k;
};
B func(){
B b;
return b;
}
int main(){
cout<< "result of func(b)-----------"<<endl;
B b=func();
return 0;
}
打印結(jié)果為
result of func(b)-----------
this is constructor without parameter of B
this is destroyor of B
2.4 初始化成員列表
必須使用 member initialization list 的情況:
- 成員是引用
- 成員是 const member
- 基類的構(gòu)造函數(shù)有一組參數(shù)
- 成員變量的構(gòu)造函數(shù)有一組參數(shù)
如果不使用 member initialization list 党巾,而是在構(gòu)造函數(shù)的函數(shù)體內(nèi)進(jìn)行賦值操作,那么編譯器可能會(huì)產(chǎn)生一個(gè)臨時(shí)對(duì)象霜医,再調(diào)用賦值操作符拷貝臨時(shí)對(duì)象齿拂,最后再將臨時(shí)對(duì)象刪除,效率會(huì)十分低下肴敛。
這里可以繼續(xù)前面的demo驗(yàn)證一下:
#include <iostream>
using namespace std;
class B{
public:
B(){
cout<< "this is constructor without parameter of B" <<endl;
}
B(int num):k(num){
cout<< "this is constructor with parameter of B" <<endl;
}
B(const B& b){
k=b.k;
cout<< "this is copy constructor of B"<<endl;
}
B& operator=(const B& b){
k=b.k;
cout << "this is operator= of B" <<endl;
return *this;
}
~B(){
cout<< "this is destroyor of B"<<endl;
}
private:
int k;
};
class A{
public:
A(int num):k(num){}
A(){
k=2;
}
private:
B k;
};
int main(){
cout<< "result of A(1)--------------"<<endl;
A* a1=new A(1);
delete a1;
cout<< "result of A()---------------"<<endl;
A* a2=new A();
delete a2;
return 0;
}
輸出結(jié)果為:
result of A(1)--------------
this is constructor with parameter of B
this is destroyor of B
result of A()---------------
this is constructor without parameter of B
this is constructor with parameter of B
this is operator= of B
this is destroyor of B
this is destroyor of B
其中署海,帶參的構(gòu)造函數(shù)使用了初始化成員列表,無(wú)參的構(gòu)造函數(shù)采取了函數(shù)體內(nèi)賦值医男,打印結(jié)果說(shuō)明后者多出了臨時(shí)對(duì)象的構(gòu)造砸狞、拷貝和析構(gòu)等步驟。
另外需注意镀梭,初始化順序按照成員的聲明順序刀森。
總結(jié)
不知不覺(jué)間,編譯器已經(jīng)幫你做到了最好报账。