1. 程式人生 > >C++物件模型之Default Constructor的建構操作

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

序言:

為了滿足編譯器的需要, 當類設計者沒有顯示的宣告Default Constructor的時候, 編譯器為滿足編譯程式的需要, 將會按照一定的規則自動生成Default Constructor。

一、編譯器自動合成Default Constructor的四種情況

1、類的成員物件包含預設建構函式

2、繼承的基類有預設建構函式

3、該類有虛擬函式, 需要初始化virtual function機制

4、該類繼承自virtual base class, 需要配置virtual base class subobject機制

☆ 注意事項:若類中有其他建構函式, 編譯器將不會生成default Constructor


1、類的成員物件包含預設建構函式 

 如果一個class沒有任何constructor,但它內含一個member object,而那個member object的類有default constructor,那麼這個class的implicit default constructor 就是“nontrivial”,編譯器需要為此class 合成出一個default constructor, 不過這個合成操作只有在constructor真正需要被呼叫時才會發生。 

如果有多個類成員物件都要求用預設建構函式進行初始化操作? 那麼在C++語言中將會按照“成員物件在class中的生命次序”來呼叫constructor, 但是編譯器自動插入這些程式碼將會插入在explicit user code之前。

class Month{ public:Month(){ cout <<" Month Construtor\n";}};
class Eye{ public:Eye(){ cout <<" Eye Construtor\n";}};
class Ear{ public:Ear(){ cout <<" Ear Construtor\n";}};
class Head{
private:
	Month month;
	Eye eye;
	Ear ear;
	int mumble;	
public:
	Head(){cout <<"美美噠";
		mumble = 2;
	}
};
而Head的預設建構函式在經過編譯器擴張後的C++虛擬碼如下

Head::Head(){
	// 編譯器自動拓展程式碼 
	month.Month::Month();
	eye.Eye::Eye();
	ear.Ear::Ear();
	// explicit user code
	cout <<"美美噠";
	mumble = 2;	
}

對以上的執行結果:



2、繼承的基類有預設建構函式 

如果類設計者把Derived設計成了沒有建構函式的程式碼,但是他的Base類(基類)有預設建構函式, 那麼編譯器將會自動為其按照基類的順序生成建構函式


3、該類有虛擬函式, 需要初始化virtual function機制 

(1)、當class宣告(或繼承)一個virtual function的時候

(2)、class 派生自一個繼承串鏈, 其中有一個或更多的virtual base classes

當出現以上情況的時候編譯器將會自動合成預設建構函式, 去構造虛擬函式表的內容。

class Animal{
public:
	virtual void run() = 0;
};
class Cat: public Animal{ public: void run(){cout << "Cat run\n";}};
class Pig: public Animal{ public: void run(){cout << "Pig run\n";}};
void run(Animal& a){
	a.run();
}

void allrun(){
	Cat c;
	Pig p;
	run(c);
	run(p);
}
(1)一個virtual function table(在cfront中稱為vtbl)會被編譯器產生出來, 記憶體class的virtual functions地址

(2) 在每個class object中, 一個額外的pointer member(也就vfptr)會被編譯器合成出來, 內含相關的virtual function table地址。


在allrun當中, Animal.run()的虛擬引發操作會被重新改寫, 以使Animal的vptr和vtbl中的run條目

// Animal.run()的虛擬引發操作的轉變
( *Animal.vptr[1] ) (&Animal)
☆ 1、代表run在virtual table中的固定索引

☆ &Animal代表需要交給“被呼叫的某個run()函式實體”的this指標

4、該類繼承自virtual base class, 需要配置virtual base class subobject機制

Virtual base clas的實現法在不同的編譯器之間有極大的差異。然而,每一種實現法的共通點在於必須使virtual base class 在其每一個derived class object中的位置,能夠於執行期準備妥當。例如下面這段程式程式碼中:

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;};
//無法在編譯時期決定(resolve)t出pa->x::i的位置
void foo(const A *pa){pa->i = 1024;} 
int main() {
	foo(new A);
	foo(new C);
	// ....
	return 0;
}
編譯器無法出定住foo() 之中“經由pa而存取的X::x”的實際偏移位置,因為pa的真正型別可以改變。編譯器必須改變“執行存取操作”的那些碼,使X::i可以延遲至執行期才決定下來.原先cfront的做法是靠“在derived class object的每一個virtual base classes中安插一個指標”完成。所有“經由reference或pointer 來存取一個virtual base class”的操作都可以通過相關指標完成.在我的例子中,foo() 可以被改寫如下,以符合這樣的實現策略:
void foo(const A* pa){pa->__vbcx->i = 1024;}
其中__vbcx表示編譯器所產生的指標, 指向virtual base class X

二、總結

1、合成建構函式只會初始化member class object和base class subobjects, 所有其他nonstatic data member(整數, 指標, 字元, 浮點等)都不會被初始化

2、任何class 如果定義default constructor, 都會被合成出來---------這是錯誤的說法

3、編譯器合成出來的預設建構函式會明確初始化每一個data member為預設值  -----這是錯誤的說法