C++ 三五法则,看看你能不能理解
簡介:三五法則規(guī)定了什么時候需要 1、拷貝構(gòu)造函數(shù) 2、拷貝賦值函數(shù) 3、析構(gòu)函數(shù)
1、需要析構(gòu)函數(shù)的類也需要拷貝構(gòu)造函數(shù)和拷貝賦值函數(shù)。
通常,若一個類需要析構(gòu)函數(shù),則代表其合成的析構(gòu)函數(shù)不足以釋放類所擁有的資源,其中最典型的就是指針成員(析構(gòu)時需要手動去釋放指針指向的內(nèi)存)。
所以,若存在自定義(且正確)的析構(gòu)函數(shù),但使用合成的拷貝構(gòu)造函數(shù),那么拷貝過去的也只是指針,此時兩個對象的指針變量同時指向同一塊內(nèi)存,指向同一塊內(nèi)存的后果很有可能是在兩個對象中的析構(gòu)函數(shù)中先后被釋放兩次。所以需要額外的拷貝控制函數(shù)去控制相應(yīng)資源的拷貝。
所以這類例子的共同點就是:一個對象擁有額外的資源(指針指向的內(nèi)存),但另一個對象使用合成的拷貝構(gòu)造函數(shù)也同時擁有這塊資源。當(dāng)一方對象被銷毀后,析構(gòu)函數(shù)釋放了資源,這時另一個對象便失去了這塊資源(但程序員還不知道)。
在上面的代碼中對象b使用合成的拷貝構(gòu)造函數(shù)拷貝對象a的值,這個程序沒有什么實際意義。
在main函數(shù)返回時,a,b變量會分別被析構(gòu),它們的成員name指向同一塊內(nèi)存,所以在程序結(jié)束時便會發(fā)生錯誤。
2、需要拷貝操作的類也需要賦值操作,反之亦然。
需要拷貝操作代表這個類在拷貝時需要進行一些額外的操作。 賦值操作 <<< = >>> 先析構(gòu)+拷貝,所以拷貝需要的賦值也需要。反之亦然。
3、析構(gòu)函數(shù)不能是刪除的
如果類的析構(gòu)函數(shù)是刪除的,那么成員便無法銷毀。所以在程序中不能定義這個類的對象。可以動態(tài)分配該對象并獲得其指針,但無法銷毀這個動態(tài)分配的對象(delete 失效)。
若上面的類的定義是
則在main函數(shù)中定義變量a,b就會發(fā)生編譯錯誤,然而,這樣的定義卻可以通過編譯
person *p;
p = new person(“me”, 20)
但是,這樣動態(tài)分配的變量是不能被釋放的,在調(diào)用 delete p 會發(fā)生編譯錯誤, 內(nèi)存泄露就這樣發(fā)生了。
如果類的某個成員的析構(gòu)函數(shù)是刪除的或不可訪問的(例 private的)則類的合成析構(gòu)函數(shù)被定義為刪除的 (小析構(gòu)NO 大析構(gòu)NO)
如果類的某個成員的拷貝構(gòu)造函數(shù)是刪除的或不可訪問的(例 private的)則類的合成拷貝函數(shù)被定義為刪除的 (小拷貝NO 大拷貝NO)
如果類的某個成員的析構(gòu)函數(shù)是刪除的或不可訪問的(例 private的)則類的合成拷貝函數(shù)被定義為刪除的 (小析構(gòu)NO 大拷貝NO)
如果類的某個成員的析構(gòu)函數(shù)是刪除的或不可訪問的(例 private的)則類的合成拷貝函數(shù)被定義為刪除的
4、如果一個類成員有刪除的或不可訪問的析構(gòu)函數(shù),那么其默認和拷貝構(gòu)造函數(shù)會被定義為刪除的。
如果沒有這條規(guī)則,可能會創(chuàng)造出無法被刪除的對象。 理論上來說,當(dāng)析構(gòu)函數(shù)不能被訪問時,任何靜態(tài)定義的對象都不能通過編譯器的編譯,所以這種情況只會出現(xiàn)在與動態(tài)分配有關(guān)的拷貝/默認構(gòu)造函數(shù)身上。
5、如果一個類有const或引用成員,則不能使用合成的拷貝賦值操作。(無法默認構(gòu)造的const成員的類 則該類就無默認構(gòu)造函數(shù))
原因很簡單,const或引用成員只能在初始化時被賦值一次,而合成的拷貝賦值操作會對所有成員都進行賦值。顯然,它不能賦值const和引用成員,所以合成的拷貝構(gòu)造函數(shù)不能被使用,即會被定義為刪除的。
本質(zhì)上,當(dāng)不可能拷貝、賦值、或銷毀類的所有成員時,類的合成拷貝控制函數(shù)就被定義成刪除的了。
以下這些內(nèi)容 來自 c++ primer 第五版一書 歸納而來 詳細內(nèi)容可以 看書 值和指針類型的類設(shè)計等
拷貝構(gòu)造函數(shù)
如果一個構(gòu)造函數(shù)的第一個參數(shù)是自身類類型的引用,且任何額外參數(shù)都有默認值,則此構(gòu)造函數(shù)是拷貝構(gòu)造函數(shù)。
拷貝構(gòu)造函數(shù)第一個參數(shù)必須是一個引用類型。此參數(shù)幾乎總是一個const的引用。拷貝構(gòu)造函數(shù)在幾種情況下都會被隱式地使用。因此,拷貝構(gòu)造函數(shù)通常不應(yīng)該是explicit的。
合成拷貝構(gòu)造函數(shù)
與合成默認構(gòu)造函數(shù)不同,即使我們定義了其他構(gòu)造函數(shù),編譯器也會為我們合成一個拷貝構(gòu)造函數(shù)。
對某些類來說,合成拷貝構(gòu)造函數(shù)用來阻止我們拷貝該類類型的對象。而一般情況,合成的拷貝構(gòu)造函數(shù)會將其參數(shù)的成員逐個拷貝到正在創(chuàng)建的對象中。每個成員的類型決定了它如何拷貝。
拷貝初始化
直接初始化和拷貝初始化的差異。
string dots(10,’,’); //直接初始化
string s(dots); //直接初始化
string s2 = dots; //拷貝初始化
當(dāng)使用直接初始化時,我們實際上是要求編譯器使用普通的函數(shù)匹配來選擇與我們提供的參數(shù)最匹配的構(gòu)造函數(shù)。當(dāng)我們使用拷貝初始化時,我們要求編譯器將右側(cè)運算對象拷貝到正在創(chuàng)建的對象中,如果需要的話還要進行類型轉(zhuǎn)換。
拷貝初始化通常使用拷貝構(gòu)造函數(shù)來完成。拷貝初始化是依靠拷貝構(gòu)造函數(shù)或移動構(gòu)造函數(shù)來完成的。
拷貝初始化不僅在我們用=定義變量時會發(fā)生,在下列情況下也會發(fā)生
將一個對象作為實參傳遞給一個非引用類型的形參。
從一個返回類型為非引用類型的函數(shù)返回一個對象。
用花括號列表初始化一個數(shù)組中的元素或一個聚合類中的成員。
參數(shù)和返回值
拷貝構(gòu)造函數(shù)被用來初始化非引用類類型參數(shù),這一特性解釋了為什么拷貝構(gòu)造函數(shù)自己的參數(shù)必須是引用類型。如果其參數(shù)不是引用類型,則調(diào)用永遠也不會成功——為了調(diào)用拷貝構(gòu)造函數(shù),我們必須拷貝它的實參,但為了拷貝實參,我們又必須調(diào)用拷貝構(gòu)造函數(shù),如此無限循環(huán)。
拷貝初始化的限制
vector v1(10); //直接初始化
vector v1 = 10; //錯誤:接受大小參數(shù)的構(gòu)造函數(shù)是explicit的
如果我們希望使用一個explicit構(gòu)造函數(shù),就必須顯式的使用:
void f(vector); //f的參數(shù)進行拷貝初始化
f(10); //錯誤:不能用一個explicit的構(gòu)造函數(shù)拷貝一個實參
(vector(10)); //正確:從一個int直接構(gòu)造一個臨時vector
如果我們希望使用一個explicit構(gòu)造函數(shù),就必須顯式的使用:
編譯器可以繞過拷貝構(gòu)造函數(shù)
編譯器被允許將下面的代碼string null_book = “9-999-99999-9”;
給寫成string null_book(“9-999-99999-9”);//編譯器略過了拷貝構(gòu)造函數(shù)。
拷貝賦值運算符
類通過拷貝賦值運算符控制其對象如何賦值。
重載賦值運算符
重載賦值運算符本質(zhì)上是函數(shù),其名字由operator關(guān)鍵字后接表示要定義的運算符的符號組成。因此,賦值運算符就是一個名為operator=的函數(shù)。類似于任何其他函數(shù),運算符函數(shù)也有一個返回類型和一個參數(shù)列表。
值得注意的是,標準庫通常要求保存在容器中的類型要具有賦值運算符,且其返回值是左側(cè)運算對象的引用。
合成拷貝賦值運算符
與處理拷貝構(gòu)造函數(shù)一樣,如果一個類未定義自己的拷貝賦值運算符,編譯器會為他它生成一個合成拷貝賦值運算符。類似拷貝構(gòu)造函數(shù),對于某些類,合成拷貝構(gòu)造運算符用來禁止該類型對象的賦值。
析構(gòu)函數(shù)
析構(gòu)函數(shù)釋放對象使用的資源,并銷毀對象的非static數(shù)據(jù)成員。
析構(gòu)函數(shù)是類的一個成員函數(shù),名字由波浪號接類名構(gòu)成。它沒有返回值,也不接受參數(shù)。
由于析構(gòu)函數(shù)不接受參數(shù),因此它不能被重載,對一個給定類,只會有唯一一個析構(gòu)函數(shù)。
析構(gòu)函數(shù)完成什么工作
如同構(gòu)造函數(shù)有一個初始化部分和一個函數(shù)體,析構(gòu)函數(shù)也有一個函數(shù)體和一個析構(gòu)部分。在一個析構(gòu)函數(shù)中,首先執(zhí)行函數(shù)體,然后銷毀成員。成員按初始化順序的逆序銷毀。
在對象最后一次使用之后,析構(gòu)函數(shù)的函數(shù)體可執(zhí)行類設(shè)計者希望執(zhí)行的任何收尾工作。通常,析構(gòu)函數(shù)釋放對象在生存期分配的所有資源。
在一個析構(gòu)函數(shù)中,不存在類似構(gòu)造函數(shù)中初始化列表的東西來控制成員如何銷毀,析構(gòu)部分是隱式的。成員銷毀時發(fā)生什么完全依賴于成員類型。銷毀類類型的成員需要執(zhí)行成員自己的析構(gòu)函數(shù)。內(nèi)置類型沒有析構(gòu)函數(shù),因此銷毀內(nèi)置類型成員什么也不需要做。
隱式的銷毀一個內(nèi)置指針類型的成員不會delete它所指向的對象。
什么時候會調(diào)用析構(gòu)函數(shù)
無論何時一個對象被銷毀,就會自動調(diào)用其析構(gòu)函數(shù):
變量在離開其作用域時被銷毀。
當(dāng)一個對象被銷毀時,其成員被銷毀。
容器(無論是標準庫容器還是數(shù)組)被銷毀時,其元素被銷毀。
對于動態(tài)分配的對象,當(dāng)對指向它的指針應(yīng)用delete運算符時被銷毀。
對于臨時對象,當(dāng)創(chuàng)建它的完整表達式結(jié)束時被銷毀。
由于析構(gòu)函數(shù)自動運行,我們的程序可以按需要分配資源,而(通常)無須擔(dān)心何時釋放這些資源。
當(dāng)指向一個對象的引用或指針離開作用域時,析構(gòu)函數(shù)不會執(zhí)行。
合成析構(gòu)函數(shù)
下面的代碼片段等價于Sales_data的合成析構(gòu)函數(shù):
在(空)析構(gòu)函數(shù)體執(zhí)行完畢后,成員會被自動銷毀。認識到析構(gòu)函數(shù)體自身并不直接銷毀成員是非常重要的。
(在學(xué)習(xí)C/C++或者想要學(xué)習(xí)C/C++可以加我們的學(xué)習(xí)交流QQ群:712263501群內(nèi)有相關(guān)學(xué)習(xí)資料)
總結(jié)
以上是生活随笔為你收集整理的C++ 三五法则,看看你能不能理解的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 《听歌六绝句·离别难》第四句是什么
- 下一篇: C++中一些你不知道的冷知识