筆記:深度探索C++物件模型 第二章

摘要

 

簡介

編譯器可能會有隱含的動作,如對 overload operator 的誤判. 因為 compiler 會自動去尋找最符合其意義的來解釋你的意圖.

 

constructor

以下是各種 default constructor 的建構情形. 一般若 class 沒有宣告 constructor, 那麼 compiler 會在需要時為他建立一個簡單的
constructor, 有四種狀況:

1. class 沒有 constructor, 但成員之中有 member object, 而該 member object 有 default constructor
時, compiler 必定會為前者合成一個 default constructor.

假設

class Dopey {...};
class Sneezy {...};
class Bashful {...};
class Snow_White {
public:
Dopey dopey;
Sneezy sneezy;
Bashful bashful;
private:
int mumble;
};

Snow_White 沒有 default constructor, 那麼 compiler 會為他合成一個 default constructor,
依序喚起 Dopey, Sneezy, Bashful 的 default constructor. 若 Snow_White 定義了 constructor
如下:

Snow_White::Snow_White:sneezy(1024) {
mumble=2048;
}

則 compiler 會擴充為

Snow_White::Snow_White();sneezy(1024) {
dopey.Dopey::Dopey();
sneezy.Sneezy::Sneezy(1024);
bashful.Bashful::Bashful();
mumble=2048;
}

2. 若 class 繼承一個帶有 default constructor 的 class, 則 compiler 會為這個 class 合成一個 constructor,
這個 constructor 只呼叫 parent class 的 constructor.

又若有多個 constructor, 但卻沒有 default constructor 時, compiler 會擴張各 constructor, 以便去呼叫必要的
default constructor. 但卻不會去合成一個新的 default constructor.

3. 若 class 有 virtual function 的時候, compiler 會在 default constructor 中把 vtable
初始化,以便讓子類別能正確呼叫到 virtual function 所對應的 function.

4. virtual base class, 各 compiler 的實作方法不同,但共通點都是要使 virtual base class 在每個 derived
class object 中的位置,能夠在 runtime 時準備妥當.例如:

class X { public: int i; };
class A: public virtual X { public: int j; };
class B: public virtual X { public: double d; };
class C:public virtual X {public: int k;};
//無法在編譯時決議(resolve)出 pa->X::i
void foo( const A* pa) { pa->i=1024; }
int
main() {
foo(new A);
foo(new C);
//...
}

所以, compiler 必須改變"執行存取動作"的程式碼,使得 X::i 能延遲到 runtime 時才決定下來.

以上四種情況都會使 compiler 必須為未宣告 constructor 的 class 合成 default constructor, 這些被 compiler
合成出來的東西, 在 C++ Standard 中稱為 implict nontrival default constructor.

除了這四種情況之外, compiler 不會合成任何 constructor.

另外在合成的 default constructor 中, 只有 base class object 和 member class object 會被初始化,
其他 nonstatic data member,如: int, int*, int array 等都不會.

新手誤解:

1.任何 class 若沒有 default constructor, 就會自動合成一個.

2. compiler合成的 default constructor 會明白設定"class內每個 data member 的預設值".

 

copy constructor

有三種情況會以一 object 內容作為另一 class object 的初值.

  1. string bb; string aa=bb;
  2. object 被當作參數交給函式時.
  3. 函數傳回值是 object 時.

當沒有提供 copy constructor 時,會直接把 member 一個個地複製到要複製的 object 上, 如:

class string {
char *str;
int len;
};

string noun("book");
string verb=noun;

實際上是

verb.str=noun.str;
verb.len=noun.len;

此處的 copy constructor 不等於 copy assignment (operator=)!!

default constructor 和 copy constructor 在必要的時候才用 compiler 產生出來.

請注意 member 若有 pointer 時,那麼預設會把指標指過去,就會有潛在的指標問題!

此種情況應宣告 explicit copy constructor 來解決此問題.如:

class string {
public:
string(const char *);
string(const string&);
};

一般 copy 會有四種情況, class 不展現出 "bitwise copy semantics"(即上述狀況):

  1. 1.class 內含一個 member object 而後者有 copy constructor 時.
  2. 2.class衍生自一個 base class, 而 base class 有 copy constructor 時.
  3. 3.class宣告一個或多個 virtual functions.
  4. 4.當 class 衍生自一個繼承串鏈,其中有一個或多個 virtual base classes.

第三種情況需考慮到之前第一章所提到的 virtual table.

因此若父類別有 animate() 和 draw()這兩個 virtual function,而子類別增加 dance(), 則不能

childclass cc1;
parentclass parent1=cc1;

會造成 virtual table 被切掉,因為 parent1 的 virtual table 根本就沒有 dance.若 parent1宣告為參考或指標時,被
compiler 合成出來的 copy constructor 會把隱含的 vptr 指向 childclass 的 virtual table, 而非
bitwise copy.這個也叫做 upcasting.

第四種情況則請回想一下 virtual base classes, 因為只會有一個實體存在,因此,compiler 會特別審慎考量,不會 bitwise
copy.

NRV最佳化,如下情況會被 compiler

x bar() {
X xx;
//....
return xx;
}

轉為如下的情況

void bar(X& __result) {
__result.X::X();
//....
return;
}

 

NRV

NRV -> Named Return Value 提供重要效率改善.

 

使用 member initialization list.

時機:

  1. 當初始化一個 reference member
  2. 當初始化一個 const member 時
  3. 當喚起一個 base class 的 constructor, 而他擁有一個參數時.
  4. 當喚起一個 member class 的 constructor, 而他擁有一個參數時.

Why:

一般這樣寫, compiler 會先產生一暫存物件, 再 assign 給 member object, 所以效率不彰.

class Word {
String _name;
int _cnt;
public:
Word(){
_name=0;
_cnt=0;
}
};

所以要用 Word::Word:_name(0) {_cnt=0;}; 效率較佳.

缺點及注意事項:

注意 initialization list 的初始順序. 若 member object 依賴性太高,最好不要放到 initialization list
中,而應移至 constructor 中.