C++中有6種特殊的成員函數(shù):默認(rèn)構(gòu)造函數(shù)、析構(gòu)函數(shù)锯仪、復(fù)制構(gòu)造函數(shù)泵督、復(fù)制賦值運算符、移動構(gòu)造函數(shù)庶喜、移動賦值運算符小腊。
這些成員函數(shù)在一些情況下會由編譯器自動生成,并且都是public的久窟。那么秩冈,哪些情況會阻礙這些成員函數(shù)的生成呢?
幾個例子
案例1
下面的代碼能否編譯通過斥扛?
class NoCopy {
public:
NoCopy() = default;
NoCopy(NoCopy&& rhs) = default; // 移動構(gòu)造函數(shù)
};
int main() {
NoCopy a;
NoCopy b(a); // ERROR
}
答案是不能入问,編譯器給出的錯誤是:
error: use of deleted function 'constexpr NoCopy::NoCopy(const NoCopy&)
意思是說,NoCopy類沒有復(fù)制構(gòu)造函數(shù)稀颁,所以不能用a來初始化b芬失。
如果刪掉移動構(gòu)造函數(shù)的聲明,則代碼可以正常編譯峻村。
這是為什么麸折?其實就是因為,移動構(gòu)造函數(shù)的聲明會導(dǎo)致編譯期不會隱式生成復(fù)制構(gòu)造函數(shù)粘昨。
案例2
我們再看一個例子垢啼,下面這個代碼窜锯,調(diào)用的是移動構(gòu)造函數(shù),還是復(fù)制構(gòu)造函數(shù)芭析?
class NoMove {
public:
NoMove() = default;
virtual ~NoMove() = default; // 顯式聲明析構(gòu)函數(shù)
private:
std::string s;
};
int main() {
NoMove a;
NoMove b(std::move(a)); // 能否調(diào)用移動構(gòu)造函數(shù)锚扎?
}
答案是,調(diào)用的是復(fù)制構(gòu)造函數(shù)馁启。原因和上例差不多驾孔,這里是因為顯式聲明了析構(gòu)函數(shù),導(dǎo)致不會隱式生成移動構(gòu)造函數(shù)惯疙。在需要移動構(gòu)造函數(shù)時翠勉,會使用復(fù)制構(gòu)造函數(shù)來代替。
你可能會想霉颠,反正都是編譯器自動生成的对碌,有必要區(qū)分嗎?
是的蒿偎,有必要朽们,這決定了會調(diào)用其成員變量s的移動構(gòu)造函數(shù)還是復(fù)制構(gòu)造函數(shù)。
自動生成規(guī)則
看了上面的例子诉位,會有個疑問骑脱,特種成員函數(shù)在什么情況下不會自動生成呢?
默認(rèn)構(gòu)造函數(shù)大家應(yīng)該都知道苍糠,只有沒有聲明任何構(gòu)造函數(shù)的情況下才會生成叁丧。
其他5個成員函數(shù)的生成更加復(fù)雜,它們之間會相互影響椿息。
如圖歹袁,紅色箭頭代表顯示聲明一個成員函數(shù)會導(dǎo)致另一個成員函數(shù)不會自動生成;綠色代表沒有影響:
為什么會有如此復(fù)雜的關(guān)系寝优?一方面是考慮“大三律”的思想条舔,另一方面是為了兼容C++98的代碼。
大三律
大三律是說:如果你聲明了復(fù)制構(gòu)造函數(shù)乏矾、賦值運算符孟抗,或析構(gòu)函數(shù)中的任意一個,你就得同時聲明所有這三個钻心。
為什么凄硼?其實很簡單,之所以顯示聲明捷沸,就是因為默認(rèn)的函數(shù)不適用摊沉,很可能說明其中包含了資源管理,例如new痒给、delete说墨。而這樣的操作一定要在這三個函數(shù)中都進行處理才正確骏全。
事實上,兩種移動操作也是同理尼斧。綜合起來姜贡,在C++11中就應(yīng)該是“大五律”了。
在C++11標(biāo)準(zhǔn)中棺棵,已經(jīng)將上圖綠色的箭頭變成deprecate了楼咳,也就是說,在將來的C++版本中烛恤,可能任意聲明這5個成員函數(shù)中的一個母怜,就不會自動生成其他幾個成員函數(shù)了。但當(dāng)前版本為了兼容C++98代碼棒动,還暫時保留圖中的綠色箭頭糙申。
使用建議
之前有提到過宾添,析構(gòu)函數(shù)最好聲明為virtual的船惨。但結(jié)合今天的例子,這又會導(dǎo)致無法自動生成兩個移動成員函數(shù)缕陕。
即使一個類現(xiàn)在沒有顯式聲明析構(gòu)函數(shù)粱锐,如果將來有一天想在析構(gòu)函數(shù)中加個日志,聲明了它扛邑,這就會導(dǎo)致原本自動生成的移動構(gòu)造函數(shù)被刪除怜浅,移動操作都變成了復(fù)制操作,可能會大幅影響性能蔬崩,僅僅是因為聲明了一個析構(gòu)函數(shù)恶座。
所以,比較穩(wěn)妥的辦法是沥阳,如果這個類可能會復(fù)制或移動跨琳,那就把兩個復(fù)制、兩個移動桐罕,以及析構(gòu)函數(shù)全都聲明脉让。如果沒有特別的需求,可以直接使用默認(rèn)實現(xiàn) =default