編譯器自動生成默認構造函數的四種情況


在以下四種情況中,如果類中沒有定義一個默認構造函數,則編譯器會自動生成一個nontrivial的默認構造函數,而不是一個不做事情的trivial默認構造函數:
1.內含一個成員變量,而這個成員變量所屬的類中含有默認構造函數,則此時需要為此類生成一個implicit default constructor(隱式的默認構造函數),這個implicit default constructor是nontrivial的,因為內含的成員變量需要進行默認構造操作。

如果內含一個成員變量a,且該成員變量a含有默認構造函數。同時已經對該類定義了默認構造函數但未對a進行操作,則編譯器會在自己定義的默認構造函數的開始部分插入一個a所屬類的默認構造函數,不然這個自己定義的默認構造函數將會忽視掉a的nontrival默認構造函數。

2.繼承自一個類,且該類中有默認構造函數。派生類中沒有定義默認構造函數,則編譯器會為派生類提供一個上一層基類的默認構造函數。

3.class中聲明了一個虛函數。聲明了虛函數代表該class中將出現vptr,並需要為虛函數構造一張虛函數表vtbl,這個操作是必須的,因此使該class的默認構造函數成為nontrivial的,因此需要為了這個vptr和vtbl構建默認構造函數,進行初始化操作。

4.帶有虛基類的class。因為虛基類的引入,必須要有一個指針或者類似索引的東西來指向虛基類的區域,以使虛基類的派生類們能找到共享的虛基類的存儲區域。

如:

class X{public:int i;};

class A:public virtual X {public:int j;};

class B:public virtual X {public:double d;};

class C:public A,public B {public:int k;};

void foo(const A* pa) {pa->i=1024};

main()

{

............

}

其中,因為pa的類型可以改變,它可以是A*,也可以是C*,因此,在編譯期並不能確定pa指向的對象到底是什么,而只能等到執行期才能固定pa->i的具體位置(因為pa可能不同,而虛基類一般是存在最底部的,因此pa->i相對於類的起始地址的偏移會隨着pa的不同而不同)。因此,為了實現在執行期能確定i的位置,需要在類中在引入一個指針或類似索引的東西,這樣,在編譯期就可以轉化為 pa->_vbcX->i=1024;而_vbcX是由編譯期所產生的指針。這樣無論pa是A*類還是C*類,他們都是經由_vbcX找到的i,只要在A中與C中都引入_vbcX,這樣他們在執行期都能通過同樣的方式來確定i的位置。

而正因為需要引入_vbcX這種形式的指針,因此使該類的初始化變得nontrivial,因此需要引入一個nontrivial的默認構造函數,使類在創建時期就構建一個_vbcX指針。

 

C++新手一般有兩個常見的誤解:

1.任何ckass如果沒有定義default constructor,就會被合成一個出來。

實際上,只有上述四種情況中才會生成默認構造函數,因為只有上述四種情況下默認構造函數才是nontrivial的,而諸如類中int a;的初始化,這對編譯期來說是trivial的,它的初始化是應該交由程序猿們來實現的。

2.編譯期合成出來的default constructor會明確設定"class內每一個data member的默認值"。

實際上,只有nontrivial的成員才需要default constructor對他進行初始化,如類中有默認構造函數的成員變量,vptr.....這種情況。

而trivial的成員的初始化工作不是編譯期的職責而是程序設計者的職責。

相反,如果class設計的默認構造函數只對trivial的成員初始化了,編譯期則會自己在所定義的默認構造函數中插入對nontrivial的初始化操作(按照聲明優先順序)。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM