class X { ... }; X x; // 明確地以一個object的內容作為另一個class object的初值 X xx = x;另兩種情況是當object被當作參數交給某個函數時,例如
extern void foo(X x); void bar() { X xx; // 以xx作為foo()第一個參數的初值(隱式的初始化操作) foo(xx); }以及當函數傳回一個 class object時。例如:
X foo_bar() { X xx; return xx; }假設 class 設計者明確定義了一個copy constructor(這是一個constructor,有一個參數的類型是其 class type),例如:
// user-defined copy constructor的實例 // 可以是多參數形式,其第二個參數及後繼參數以一個默認值供應之 X::X(const X &x); Y::Y(const Y &y, int = 0);那麼在大部分情況下,當一個 class object以另一個同類實體作為初值時,上述的constructor會被調用。這可能會導致一個暫時性 class object的產生或程序代碼的蛻變。
class String { public: // ... 沒有 explicit copy constructor private: char *str; int len; };一個String object的default memberwise initialization發生在這種情況下:
String noun(book); String verb = noun; 其完成方式就像個別設定每一個members一樣: verb.str = noun.str; verb.len = noun.len;如果一個String object被聲明為另一個 class 的member,如下所示:
class Word { public: // ... 沒有 explicit copy constructor private: int _occurs; String _word; // String object成為class Word的一個member };那麼一個Word object的default memberwise initialization會拷貝其內建的member _occurs,然後再從String member object _word遞歸實施memberwise initialization。
#include Word.h Word noun(book); void foo() { Word verb = noun; }很明顯verb是根據noun來初始化,但在尚未看到 class Word聲明之前,不可能預測這個初始化操作的程序行為,如果 class Word的設計者定義了一個copy constructor,verb的初始化操作會調用它,但如果該 class 沒有定義explicit copy constructor,那麼是否會有一個編譯器合成的實體被調用呢?這就視該 class 是否展現bitwise copy semantics而定。如下所示:
// 以下聲明展現了bit copy semantics class Word { public: Word(const char *); ~Word() { delete []str; } private: int cnt; char *str; };這種情況下並不需要合成出一個default copy constructor,因為上述聲明展現了default copy semantics,因此verb的初始化操作就不需要一個函數調用,然而,如果 class Word是這樣聲明的:
// 以下聲明並未展現出bitwise copy semantics class Word { public: Word( const String &); ~Word(); private: int cnt; String str; }; 其中String聲明了一個explicit copy constructor: class String { public: String(const char *); String(const String &); ~String(); };在這種情況下,編譯器必須合成出一個copy constructor以便調用member class String object的copy constructor:
// 一個被合成出來的copy constructor // C++偽代碼 inline Word::Word(const Word &wd) { str.String::String(wd.str); cnt = wd.cnt; }有一點值得注意:在這被合成出來的copy constructor中,如整數、指針、數組等等的nonclass members也都會被復制。
class ZooAnimal { public: ZooAnimal(); virtual ~ZooAnimal(); virtual void animate(); virtual void draw(); private: // ZooAnimal的animate()和draw() // 所需要的數據 }; class Bear : public ZooAnimal { public: Bear(); void animate(); void draw(); virtual void dance(); private: // Bear的animate()和draw()和dance() // 所需要的數據 };ZooAnimal class object以另一個ZooAnimal class object作為初值,或Bear class object以另一個Bear class object作為初值,都可以直接靠bitwise copy semantics完成。例如:
Bear yogi; Bear winnie = yogi;yogi會被 default Bear constructor初始化,而在constructor中,yogi的vtpr被設定指向Bear class 的 virtual table。因此,把yogi的vptr值拷貝給winnie的vptr是完全的。
ZooAnimal franny = yogi; // 這會發生切割(sliced)franny的vptr不可以被設定指向Bear class 的virtual table,否則當下面程序片段中的draw()被調用而franny被傳進去時,就會炸毀(blow up)
void draw (const ZooAnimal &zoey) { zoey.draw(); } void foo() { // franny的vptr指向ZooAnimal的virtual table // 而非Bear的virtual table ZooAniaml franny = yogi; draw(yogi); //調用Bear::draw() draw(franny); //調用ZooAnimal::draw() }通過franny調用virtual function draw(),調用的是ZooAnimal實體而非Bear實體(雖然franny是以Bear object yogi作為初始值)。因為franny是一個ZooAnimal object。事實上,yogi中的Bear部分已經在franny初始化時被切割(sliced)。如果franny被聲明為一個reference(或者如果它是一個指針,而其值為yogi的地址),那麼經由franny所調用的draw()才會是Bear的函數實體。
class Raccon : public virtual ZooAnimal { public: Raccon(){ /* 設定private data初值 */ } Racccon(int val) { /* 設定private data初值 */ } // ... private: // 所需要的數據 };編譯器所產生的代碼(用以調用ZooAnimal的default constructor,將Racccon的vptr初始化,並定位出Raccon中的ZooAnimal subject)被插入在兩個Raccon constructors之間。
class RedPanda : public Raccon { public: RedPanda() { /* 設定private data初值 */ } RedPanda(int val) { /*設定private data初值 */ } private: // ... };如果以一個Reccon object作為另一個Raccon object的初值,那麼bitwise copy就戳戳有余了
// 簡單的bitwise copy就足夠 Raccon rocky; Raccon little_critter = rocky;然而如果企圖以一個RedPanda object作為little_critter的初值,編譯器必須判斷後續當程序員企圖存取其ZooAnimal subobject時是否能夠正確地執行
// 簡單的bitwise copy還不夠 // 編譯器必須明確地將litte_critter的virtual base class pointer/offset初始化 RedPanda little_red; Raccon little_critter = little_red;在這種情況下,為了完成正確的little_critter初值設定,編譯器必須合成一個copy constructor,插入一些碼以設定 virtual base class pointer/offset的初值,對每一個members執行必要的memberwise初值化操作,以及執行其它的內存相關操作(3.4對於 virtual base classes有更詳細的討論)
// 簡單的bitwise copy可能夠用,可能不夠用 Raccon *ptr; Raccon little_critter = *ptr;當一個初始化操作存在並保持著bitwise copy semantics的狀態時,如果編譯器能夠保證object有正確而相等的初始化操作,是否它應該抑制copy constructor的調用,以使其所產生的程序代碼優化?