05:了解C++默默編寫并調用哪些函數
1:一個空類,如果你自己沒聲明,編譯器就會為它聲明(編譯器版本的)一個copy構造函數、一個copy assignment操作符和一個析構函數。此外如果你沒有聲明任何構造函數,編譯器也會為你聲明一個default構造函數。所有這些函數都是public且inline的。
2:隻有當這些函數被調用時,它們才會被編譯器建立出來。
3:編譯器生成的default構造函數和析構函數主要是給編譯器一個地方用來放置“藏身幕後”的代碼,比如用base classes和non-static成員變量的構造函數和析構函數。至于copy構造函數和copy assignment操作符,編譯器建立的版本隻是單純的将來源對象的每一個non-static成員變量拷貝到目标對象。
4:編譯器建立的析構函數是個non-virtual,除非這個class的base class自身聲明有virtual析構函數。
5:以下情況下,編譯器會拒絕為class生出operator=:
a、類中具有引用成員或者const 常量成員:
template<class T>
class NamedObject {
public:
NamedObject(std::string& name, const T& value);
...
private:
std::string& nameValue; // this is a reference
const T objectValue; // this is const
};
引用和常量必須在定義時進行初始化,不支援指派操作。是以編譯器拒絕為這樣的類建立operator=函數;
如果你打算在一個包含reference成員或const成員的class内支援指派操作,你必須自己定義copy assignment操作符。
b、如果某個base class将copy assignment操作符聲明為private,編譯器也拒絕為其derived class生成一個copy assignment操作符。
06:若不想使用編譯器自動生成的函數,就應該明确拒絕
1:如果不希望class支援複制初始化或者指派操作,因為不定義copy構造函數和copy assignment操作符,編譯器會自動建立一個,是以不定義這倆函數達不到這個目的。
2:因為編譯器建立的函數都是public的,為了阻止這些函數被建立出來,可以将copy構造函數或copy assignment操作符聲明為private。這樣便阻止了編譯器建立這些函數,而且類的使用者也無法調用它們。
3:上面的做法還是有漏洞,因為類的成員函數和友元函數還是可以調用你的private函數。這種情況下,可以僅僅聲明而不去定義它們。這種情況下,如果有成員函數或友元函數調用它們的話,将會産生一個連接配接錯誤。
是以,将複制構造函數和指派操作符聲明為private且不去定義它們,當類的使用者企圖拷貝時,編譯器會阻止他;如果在成員函數或友元函數中這麼做,連接配接器會發出抱怨。
4:将連接配接期錯誤移至編譯期是可能的(而且那時好事,畢竟越早偵測出錯誤越好),隻要定義一個Uncopyable類,并将自己的類繼承該類就好:
class Uncopyable {
protected: // allow construction
Uncopyable() {} // and destruction of
~Uncopyable() {} // derived objects...
private:
Uncopyable(const Uncopyable&); // ...but prevent copying
Uncopyable& operator=(const Uncopyable&);
};
任何人(包括成員函數或友元函數)嘗試拷貝Uncopyable類的派生類對象時,編譯期便試着生成一個copy構造函數和一個copy assignment操作符。這些函數的編譯器生成版會嘗試調用其base class的對應函數,那些調用會被編譯器拒絕,因為其base class的拷貝函數是private。
07:為多态基類聲明virtual析構函數
1:當derived class對象經由一個base class指針删除,而該base class帶着一個non-virtual析構函數,則其結果是未定義的。實際執行時通常發生的是對象的derived成分沒被銷毀,而其base class成分通常會被銷毀,于是造成一個詭異的“局部銷毀”對象。
2:任何class隻要帶有virtual函數都幾乎确定應該也有一個virtual析構函數。
3:如果class不含virtual函數,通常表示它并不願意被用做一個base class。當class不企圖被當作base class時,令其析構函數為virtual往往是個饅主意。
欲實作出virtual函數,對象必須攜帶某些資訊,主要用來在在運作期決定哪一個virtual函數該被調用。這份資訊通常是由一個所謂vptr ( virtual table pointer)指針指出。vptr指向一個由函數指針構成的數組,稱為vtbl ( virtual table );每一個帶有virtual函數的class都有一個相應的vtbl。當對象調用某一virtual函數,實際被調用的函數取決于該對象的vptr所指的那個vtb----編譯器在其中尋找适當的函數指針。
是以,無端的将某個class的析構函數聲明為virtual,會增加對象的體積(vptr)。是以許多人的心得是:隻有當class内含至少一個virtual函數,才為它聲明virtual析構函數。
4:标準string,以及所有STL容器如vector,list,set,map等等,都不virtual析構函數,是以,不應該将它們當做base class。
5:如果抽象基類聲明了virtual析構函數,則必須為它提供一份定義:
class AWOV {
public:
virtual ~AWOV() = 0; // declare pure virtual destructor
};
AWOV::~AWOV() {} // definition of pure virtual dtor
這是因為,派生類繼承該抽象基類後,派生類對象銷毀時,會首先調用派生類的析構函數,然後是基類的析構函數。是以編譯器會在AWOV的derived classes的析構函數中建立一個對~AWOV的調用,是以必須提供一份定義,否則會連接配接錯誤。
08:别讓異常逃離析構函數
C++并不禁止析構函數吐出異常,但它不鼓勵這麼做。如果某個類的析構函數有可能抛出異常,則要麼:抛出異常時直接調用abort退出程式;要麼抛出異常時吞下異常,僅記錄日志。
09:絕不在構造和析構過程中調用virtual函數
1:不要再構造函數和析構函數中,調用virtual函數。
2:在base class構造期間,如果構造函數中調用了virtual函數,即使目前正在構造derived class(構造派生類對象時,需要首先構造其基類部分),virtual函數也是base class中的版本。也就是說;在base class構造期間,virtual函數不是virtual函數。
在derived class對象的base class構造期間,對象的類型是base class而不是derived class。不隻virtual函數會被編譯器解析至(resolve to)base class,若使用運作期類型資訊(runtime type information,例如dynamic_cast和typeid),也會把對象視為base class類型。
3:相同道理也适用于析構函數。一旦derived class析構函數開始執行,對象内的derived class成員變量便呈現未定義值,是以C++視它們仿佛不再存在。進入base class析構函數後對象就成為一個base class對象。
4:确定你的構造函數和析構函數都沒有調用virtual函數,而它們調用的所有函數也都要服從這一限制。
10:令operator=傳回一個reference to *this
1:指派時,可以将其寫成連鎖形式:x = y = z = 15;指派采用右結合律,是以這個表達式等價于:x = ( y = ( z = 15 ) );
2:為了實作“連鎖指派”,指派操作符必須傳回一個reference指向操作符的左側實參。這是你為classes實作指派操作符時應該遵循的協定:
Widget& operator=(const Widget& rhs) // return type is a reference to
{ // the current class
...
return *this; // return the left-hand object
}
3:這個協定不僅适用于标準的指派形式,也适用于所有指派相關運算,比如+=。
11:在operator=中處理“自我指派”
1:“自我指派”發生在對象被指派給自己時,不要認定客戶絕不會那麼做,而且自我指派并不總是那麼可被一眼辨識出來,例如:a[i] = a[j];這條語句中,如果i和j相同,這便是自我指派;再比如:*px = *py;如果px和py恰巧指向相同,這也是自我指派。
2:“自我指派”時,可能會掉進“在停止使用資源之前意外釋放了它”的陷阱。比如:
Widget& Widget::operator=(const Widget& rhs)
{
delete pb;
pb = new Bitmap(*rhs.pb);
return *this;
}
這裡的自我指派問題是,operator=函數内的*this和rhs有可能是同一個對象。果真如此delete就不隻是銷毀目前對象的bitmap,它也銷毀rhs的bitmap。
欲阻止這種錯誤,傳統做法是藉由operator=最前面的一個“證同測試(identity test )”達到“自我指派”的檢驗目的:
Widget& Widget::operator=(const Widget& rhs)
{
if (this == &rhs) return *this; // identity test: if a self-assignment,
// do nothing
delete pb;
pb = new Bitmap(*rhs.pb);
return *this;
}
3:這個新版本仍然存在異常方面的麻煩。更明确地說,如果”new Bitmap”導緻異常(不論是因為配置設定時記憶體不足或因為Bitmap的copy構造函數抛出異常),Widget最終會持有一個指針指向一塊被删除的Bitmap。
令人高興的是,讓operator=具備“異常安全性”往往自動獲得“自我指派安全”的回報。是以愈來愈多人對“自我指派”的處理态度是傾向不去管它,把焦點放在實作“異常安全性”上。例如,我們隻需注意在複制pb所指東西之前别删除pb:
Widget& Widget::operator=(const Widget& rhs)
{
Bitmap *pOrig = pb; // remember original pb
pb = new Bitmap(*rhs.pb); // make pb point to a copy of *pb
delete pOrig; // delete the original pb
return *this;
}
現在,如果”new Bitmap”抛出異常,pb保持原狀。即使沒有證同測試,這段代碼還是能夠處理自我指派,因為我們對原bitmap做了一份複件、删除原bitmap、然後指向新制造的那個複件。它或許不是處理“自我指派”的最高效辦法,但它行得通。
4:在operator=函數内確定代碼不但“異常安全”而且“自我指派安全”的一個替代方案是,使用所謂的copy and swap技術。
它是一個常見而夠好的operator=撰寫辦法:
Widget& Widget::operator=(const Widget& rhs)
{
Widget temp(rhs); // make a copy of rhs's data
swap(temp); // swap *this's data with the copy's
return *this;
}
或者,可能更常見的是下面這種寫法:
Widget& Widget::operator=(Widget rhs)
{
swap(rhs);
return *this;
}
12:複制對象時勿忘其每一個成分
1:如果自己寫複制構造函數或指派操作符而不使用編譯器的版本,則需要注意的是:如果你為class添加一個成員變量,你必須同時修改複制構造函數和指派操作符函數(你也需要修改class的所有構造函數,以及任何非标準形式的operator=(比如+=))。如果你忘記,編譯器不太可能提醒你。
2:任何時候隻要你承擔起“為derived class撰寫copying函數”的重責大任,必須很小心地也複制其base class成分。那些成分往往是private,是以你應該讓derived class的copying函數調用相應的base class函數。
轉載于:https://www.cnblogs.com/gqtcgq/p/7573115.html