1. 程式人生 > >深入理解C++物件模型之建構函式

深入理解C++物件模型之建構函式

一、前言

    學習C++的同學一般都知道有建構函式這個東西,我相信很多同學的理解就是建構函式是用來初始化類成員的,是的,建構函式的本質確實是這樣的,但很多同學會有以下兩個誤解:

        (1)任何class如果沒有定義任何建構函式,編譯器就會幫你自動生成一個;

        (2)編譯器用合成出來的預設建構函式會“Class內的每一個data member”;

先不說這兩個觀點對不對,但至少他不嚴謹。

二、建構函式與預設建構函式

    這裡首先引出C++Primer裡面的關於建構函式的介紹:建構函式的任務是初始化類物件的資料成員,無論什麼時候只要class的物件被建立,就會執行建構函式。我的理解是這裡所說的建構函式是User Constructor Function,即使用者定義的建構函式,與其相對應的還有一種叫做預設建構函式

。在網上看到關於預設建構函式的定義為:可以不用實參進行呼叫的建構函式,其包括兩種情況:(1)沒有帶明顯形參的建構函式;(2)提供了預設實參的建構函式。

    這裡之所以要介紹預設建構函式,是因為使用者可以自己定義一個預設建構函式,而編譯器也可以為我們合成一個預設建構函式,其實編譯器合成的建構函式確實是都不帶明顯形參的,因此我們經常把編譯器合成的建構函式稱為“合成預設建構函式”

三、編譯器需要的建構函式和程式需要的建構函式

    使用者定義一個以下類和它的建構函式

class A
{
public:

private:
    int a;
    int b;
};

class A沒有定義建構函式

,按照前言中,大多數同學的第一個誤解,即任何class如果沒有定義預設建構函式,編譯器就會幫你自動生成一個,OK,那麼生成一個預設建構函式幹什麼呢?同學的潛意識裡是要求編譯器用生成的這個建構函式去初始化成員變數a和b。那這麼說是不是就能用這個類去例項化一個物件呢?因為只有例項化物件的時候才需要呼叫建構函式啊,這才是你自以為的編譯器合成的建構函式的用武之地啊。下面在VS2010IDE中進行驗證,結果是這樣的:

#include <iostream>
using namespace std;
class A
{
public:

public:
    int a;
    int b;
	
};

void main()
{
    A obj1;
    cout<<"obj1.a = "<<obj1.a<<endl;
}


    What?為什麼結果是這樣的呢?物件obj1沒有被初始化?不是說編譯器會生成一個預設建構函式來初始化他的成員變數嗎?其實實際上編譯器並沒有合成預設建構函式,因為你所謂的編譯器會合成一個建構函式來初始化其成員變數,那是你認為,自以為的,並不是編譯器以為的,也就不是編譯器需要的,而是程式的本身需要,你要訪問物件obj1的成員,程式當然需要先對其初始化,但是程式需要並不等於編譯器需要

    因此,這就可以推翻同學們的第一個誤解,很多時候編譯器並沒有幫你生成預設建構函式,即使你沒有定義任何建構函式,因為這不是編譯器的必須工作(儘管編譯器揹著你幹了很多有利於你的事情,但它也不是傻子,不是它的事情,它肯定不會幹)

    那既然編譯器不會幫你合成預設建構函式,那為什麼有很多人這樣說呢?我想這應該是斷章取義的結果。在《深入理解C++物件模型》中,侯捷大師引用C++ standard很明確的給了我們答案。第一個說明是:對於Class X,如果沒有任何使用者宣告的建構函式,那麼會有一個預設建構函式被隱式宣告出來,……一個被隱式宣告的預設建構函式是trivial(淺薄無能的、沒啥用的)建構函式。這就和同學的第一點誤解有很大的相似性,但僅僅是相似,而完全不同,因為這裡說的只是宣告出一個trivial的建構函式,並不是為誤解了的會合成出一個預設建構函式,聲明瞭並不代表一定要合成(即定義)

    那麼是不是都不會合成呢?也不是,候老師給的解答是:預設建構函式只有在需要的時候被編譯器產生(合成、定義)出來。關鍵字眼是“在需要的時候”,被誰需要?那當然是被編譯器需要,那什麼情況下編譯器才是需要的呢?就是下面四種情況:

    (1)帶有預設建構函式的類成員物件。即如果一個Class A沒有定義任何建構函式,但它含有一個Class B物件成員,而Class B有預設建構函式,那麼Class A的預設建構函式就是被編譯器需要的,因此編譯會合成Class A的預設建構函式。這裡Class A和B分別定義如下

class B
{
public:
    B()
    {
        m=1;
        n=2;
    }
private:
    int m;
    int n;
};
class A
{
public:
public:
    int a;
    int b;
    B obj2;
};
現在再訪問Class A的物件,如
void main()
{
	A obj1;
	cout<<obj1.a<<endl;
}
不會再出現Class obj1未定義的錯誤,但是輸出的結果為未知數,比如我的執行結果是:a=-858999460;這是一個未定義的整數,為什麼呢?程式沒有報錯,說明Class A的物件obj1已經正確被構造,也說明編譯器已經為我們合成了預設建構函式,初始化了Class B的物件obj2,但為什麼沒有初始化自己的成員物件a和b呢?還是那句話,編譯器只做自己該做的事情,不是它的事情它不會做,而成員變數初始化時程式需要的,則應該有程式設計師來完成,就算編譯器合成了一個有用的預設建構函式,它也不會初始化變數a和b,它只負責呼叫Class B的預設建構函式來初始化Class B的物件obj2。如下面的程式執行
void main()
{
    A obj1;
    cout<<"obj1.a = "<<obj1.a<<endl;
    cout<<"obj2.m = "<<obj1.obj2.m<<endl;
}
結果為:(能充分說明上述的分析)

上述結論,也能推翻同學們對前言中的第二個誤解,編譯器用合成出來的預設建構函式會“Class內的每一個data member”,其實編譯器它只做自己該做的事情。

    (2)帶有預設建構函式的基類。如果一個沒有任何建構函式的Class是繼承於一個帶有預設建構函式的基類,那麼派生類的預設建構函式也是被編譯器需要的,編譯器需要為使用者合成一個預設建構函式。

        因為構造子類之前需要先構造基類,即使派生類沒有定義任何建構函式,編譯器也會合成一個預設建構函式,用來呼叫基類的預設建構函式。

    (3)帶有虛擬函式的Class。即如果一個Class定義了虛擬函式,那麼編譯器也要合成預設建構函式。

    (4)虛擬繼承體系中的派生類,編譯器也需要合成預設建構函式。

        對於情況(3)和(4),編譯器之所以要生成預設建構函式,是因為編譯器需要在合成的預設建構函式中對虛擬函式和虛擬繼承機制進行支援,即需要設定或者重置虛擬函式表或者虛基類指標。

相關推薦

深入理解C++物件模型建構函式

一、前言     學習C++的同學一般都知道有建構函式這個東西,我相信很多同學的理解就是建構函式是用來初始化類成員的,是的,建構函式的本質確實是這樣的,但很多同學會有以下兩個誤解:         (1)任何class如果沒有定義任何建構函式,編譯器就會幫你自動生成一個;

C++物件模型虛擬函式實現原理

在C++中,多型(polymorphism)的意思是,用基類的指標或者引用,定址出一個派生類物件。而虛擬函式(virtual member function)是多型的基礎,這也是面向物件程式設計迷人之處。現在剛好有時間,就寫一下自己對C++在單一繼承情況下如何實現虛擬函式的

C++物件模型 Copy Constructor的建構操作

序言: 在計算機當中, 當一個class object的內容作為另一個class object的初始值的時候, 如果class 設計者沒有顯示的宣告Copy Constructor, 那麼編譯器將會自動生成Copy Constructor。 那麼在這裡問題就來了: 1、 在什麼情況下會呼

C++物件模型Default Constructor的建構操作

序言: 為了滿足編譯器的需要, 當類設計者沒有顯示的宣告Default Constructor的時候, 編譯器為滿足編譯程式的需要, 將會按照一定的規則自動生成Default Constructor。 一、編譯器自動合成Default Constructor的四種情況 1、類的成員物件包

深入探索C++物件模型(九) 解構函式 (以及顯式定義的解構函式問題、解構函式Rules of Three)

  如果類沒有定義解構函式,那麼只有類中含有成員物件(或者本類的基類)擁有解構函式的情況下,編譯器才會合成一個出來,否則解構函式被視為不要,也就不需要合成。例如,如下類,雖然Point類擁有虛擬函式:class Point {  piblic:       Point(flo

深入c++物件模型執行期語意學

1.物件的構造與解構   一般而言,constructor 和 destructor的安插都會如你所預期:       //c++偽碼     {       Point point;       //point.Point::Point();一般而言會被安插在這裡    

06深入理解C指針---指針類型和長度

特征 都是 負數 意義 參數類型 同時 print 相關 通過   該系列文章源於《深入理解C指針》的閱讀與理解,由於本人的見識和知識的欠缺可能有誤,還望大家批評指教。   如果考慮到程序的可移植性和跨平臺性時,指針長度就是一個問題,需要慎重處理。一般情況下,數據指針的長度

C++物件模型記憶體佈局(3)

轉載地址:https://mp.weixin.qq.com/s/dTyAC2IQ50c9nmQGOC0c2A   經過兩天的摸索,今天終於搞清楚C++物件模型.前兩篇C++物件模型之記憶體佈局(2)C++物件模型之記憶體佈局(1)(請戳我)已經講解了單繼承,多重繼承和多繼承的物件模

C++物件模型記憶體佈局(2)

轉載地址:https://mp.weixin.qq.com/s/UQhTAXIHffN3Now4_utb6g   在C++物件模型之記憶體佈局(1)一文中分別講了無多型和有多型時單繼承的物件記憶體佈局,這篇文章將深入講解多重繼承和多繼承.   多重繼承 &nb

C++物件模型記憶體佈局(1)

轉載地址: https://mp.weixin.qq.com/s/LMJ4Hsa1hmued2egk9uWMQ   如果想學習在linux或者在linux平臺下開發,學習C/或C++是非常好的選擇.俗話說,術業有專攻,學一門技術,就儘量學得深,也可以作為行走江湖,混口飯吃的一項本領

深入探索C++物件模型(五)

https://www.cnblogs.com/lengender-12/p/6970496.html 構造、解構、拷貝語意學(Semantics of Construction,Destruction, and Copy) 一般而言,class的data member應該被初始化,並且只在

C++物件模型記憶體佈局三(虛繼承)

經過兩天的摸索,今天終於搞清楚C++物件模型.前兩篇已經講解了單繼承,多重繼承和多繼承的物件模型.今天講解菱形繼承,雖然過程艱難,但是收穫豐富. 簡單虛繼承物件模型 首先編寫如下的測試程式: 1

深入探索C++物件模型:Default-Constructor的構造操作

那麼,什麼時候才會合成出一個default constructor呢?——當編譯器需要它的時候!此外,被合成出來的constructor只執行編譯器所需的行動。 [x] “任何class如果沒有定義default constructors,就會被合成出一個來”?——錯 [x] “編譯

深入探索C++物件模型(四)

https://www.cnblogs.com/lengender-12/p/6959222.html Function語意學(The Semantics of Function) static member functions不可能做到的兩點:(1)直接存取n

深度探索C++物件模型--物件的差異加上多型之後

class zooAnimal{ public: zooAnimal(); virtual ~zoonAnimal(); virtual void rotate(); protected: int loc; string name; }; class Bear :public zooAn

C++札記】C++物件模型記憶體佈局

對於C++的學習,我看了C++ Primer之後,進階的書為深入理解C++物件模型,這本書講解了C++類在記憶體中是如何佈局以及成員函式是怎麼呼叫,有助於理解C++多型是如何實現的.總之,受益匪淺.     無多型的物件佈局 單個類: 假設有以下一個類的定義:

深入理解Java記憶體模型系列篇

確保對記憶體的讀-改-寫操作原子執行。在Pentium及Pentium之前的處理器中,帶有lock字首的指令在執行期間會鎖住匯流排,使得其他處理器暫時無法通過匯流排訪問記憶體。很顯然,這會帶來昂貴的開銷。從Pentium 4,Intel Xeon及P6處理器開始,intel在原有匯流排鎖的基礎上做了一個很有意

C++物件模型RTTI的實現原理

RTTI是Runtime Type Identification的縮寫,意思是執行時型別識別。C++引入這個機制是為了讓程式在執行時能根據基類的指標或引用來獲得該指標或引用所指的物件的實際型別。但是現在RTTI的型別識別已經不限於此了,它還能通過typeid操作符識別出所有的

[讀書筆記] 深入探索C++物件模型-第一章《關於物件

最新在看深入探索C++物件模型(Inside C++ object model),看的同時針對一些之前沒有留意或者理解不深的內容整理一下讀書筆記,方便之後複習,也希望可以幫助到有同樣疑惑的人。 下面是

[讀書筆記] 深入探索C++物件模型-第四章-Function語義學(中)

繼續整理第四章的內容,注:以下部分圖片來自於原文 1. 單繼承情況下的虛擬函式呼叫:  對於多型虛擬函式的呼叫(通過基類指標或者引用),例如ptr->z();,需要知道兩個資訊:     a. ptr所指物件的真實型別,這可以使我們選擇正確的z()實體;     b.