c++三五法则(适合学完一遍不怎么清楚的童鞋)
? ? ? ?有三個基本操作可以控制類的拷貝操作:拷貝構造函數、拷貝賦值運算符和析構函數。而且,在新標準下,一個類還可以定義一個移動構造函數和一個移動賦值運算符。這些操作通常應該被看作一個整體。通常,只需要其中一個操作,而不需要定義所有操作的情況是很少見的。?
? ? ? ?需要析構函數的類也需要拷貝和賦值操作。當我們決定一個類是否需要定義它自己版本的拷貝控制成員時,一個基本原則是首先確定這個類是否需要一個析構函數。如果一個類需要自定義析構函數,幾乎可以肯定它也需要自定義拷貝賦值運算符和拷貝構造函數。?
? ? ? ?需要拷貝操作的類也需要賦值操作,反之亦然。?
? ? ? ?如果一個類需要一個拷貝構造函數,幾乎可以肯定它也需要一個拷貝賦值運算符。反之亦然——如果一個類需要一個拷貝賦值運算符,幾乎可以肯定它也需要一個拷貝構造函數。然而,無論是需要拷貝構造函數還是需要拷貝賦值運算符都不必然意味著也需要析構函數。
1. 需要析構函數的類也需要拷貝構造函數和拷貝賦值函數。
? ? ? ?通常,若一個類需要析構函數,則代表其默認的析構函數不足以釋放類所擁有的資源,其中最典型的就是指針成員(析構時需要手動去釋放指針指向的內存)。所以,若存在自定義(且正確)的析構函數,但使用默認的拷貝構造函數,那么拷貝過去的也只是指針,此時兩個對象的指針變量同時指向同一塊內存,指向同一塊內存的后果很有可能是在兩個對象中的析構函數中先后被釋放兩次。所以需要額外的拷貝控制函數去控制相應資源的拷貝。
? ? ? ? 所以這類例子的共同點就是:一個對象擁有額外的資源(指針指向的內存),但另一個對象使用默認的拷貝構造函數也同時擁有這塊資源。當一方對象被銷毀后,析構函數釋放了資源,這時另一個對象便失去了這塊資源(但程序員還不知道)。
class person { public:std::string *name;int age;person(const char* the_name, int the_age){name = new std::string(the_name);age = the_age;}~person(){delete name;} };int main(void) {person a("me", 20);person b(a);std::cout << *b.name << std::endl;return 0; }
? ? ? ?在上面的代碼中對象b使用默認的拷貝構造函數拷貝對象a的值,這個程序沒有什么實際意義。在main函數返回時,a,b變量會分別被析構,它們的成員name指向同一塊內存,所以在程序結束時便會發生錯誤。
2. 需要拷貝操作的類也需要賦值操作,反之亦然。需要拷貝操作代表這個類在拷貝時需要進行一些額外的操作。賦值操作=先析構+拷貝,所以拷貝需要的賦值也需要。反之亦然。
3. 析構函數不能是私有的。如果類的析構函數是私有的,那么成員便無法銷毀。所以在程序中不能定義這個類的對象。可以動態分配該對象并獲得其指針,但無法銷毀這個動態分配的對象(delete 失效)。若上面的類的定義是
class person { public:std::string *name;int age;person(const char* the_name, int the_age){name = new std::string(the_name);age = the_age;} private:~person(){delete name;} };則在main函數中定義變量a,b就會發生編譯錯誤,然而,這樣的定義卻可以通過編譯?
person *p;? p = new person("me", 20)?但是,這樣動態分配的變量是不能被釋放的,在調用 delete p 會發生編譯錯誤, 內存泄露就這樣發生了。
4. 如果一個類有私有的或不可訪問的析構函數,那么其默認和拷貝構造函數會被定義為私有的。
? ? ? ? 如果沒有這條規則,可能會創造出無法被私有的對象。 理論上來說,當析構函數不能被訪問時,任何靜態定義的對象都不能通過編譯器的編譯,所以這種情況只會出現在與動態分配有關的拷貝/默認構造函數身上。
5. 如果一個類有const或引用成員,則不能使用默認的拷貝賦值操作。
原因很簡單,const或引用成員只能在初始化時被賦值一次,而默認的拷貝賦值操作會對所有成員都進行賦值。顯然,它不能賦值const和引用成員,所以默認的拷貝構造函數不能被使用,即會被定義為私有的。
C++ 三/五法則(總結的真棒)
當定義一個類時,我們顯式地或隱式地指定了此類型的對象在拷貝、賦值和銷毀時做什么。一個類通過定義三種特殊的成員函數來控制這些操作:拷貝構造函數、拷貝賦值運算符和析構函數。
拷貝構造函數定義了當用同類型的另一個對象初始化新對象時做什么,拷貝賦值運算符定義了將一個對象賦予同類型的另一個對象時做什么,析構函數定義了此類型的對象銷毀時做什么。我們將這些操作稱為拷貝控制操作。
由于拷貝控制操作是由三個特殊的成員函數來完成的,所以我們稱此為“C++三法則”。在較新的 C++11 標準中,為了支持移動語義,
又增加了移動構造函數和移動賦值運算符,這樣共有五個特殊的成員函數,所以又稱為“C++五法則”。
也就是說,“三法則”是針對較舊的 C++89 標準說的,“五法則”是針對較新的 C++11 標準說的。
為了統一稱呼,后來人們把它叫做“C++ 三/五法則”。
“需要析構函數的類也需要拷貝和賦值操作”
從“需要析構函數”可知,類中必然出現了指針類型的成員(否則不需要我們寫析構函數,默認的析構函數就夠了),所以,我們需要自己寫析構函數來釋放給指針所分配的內存來防止內存泄漏。
那么為什么說“也需要拷貝構造函數和賦值操作”呢?原因是:類中出現了指針類型的成員,必須防止淺拷貝問題。所以需要自己書寫拷貝構造函數和拷貝賦值運算符,而不能使用默認的拷貝構造函數和默認的拷貝賦值運算符。
“需要拷貝操作的類也需要賦值操作,反之亦然”
“析構函數不能是刪除的成員”
如果析構函數是刪除的,那么無法銷毀此類型的對象。對于一個刪除了析構函數的類型,編譯器不允許定義該類型的變量或創建該類的臨時對象。而且,如果一個類有某個成員的類型刪除了析構函數,我們也不能定義該類的變量或臨時對象。
總結
以上是生活随笔為你收集整理的c++三五法则(适合学完一遍不怎么清楚的童鞋)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: xhtml使用style属性
- 下一篇: C++ const成员和引用成员