《深度探索C++物件模型》讀書筆記(2)

來源:互聯網
上載者:User
《深度探索C++物件模型》讀書筆記(2)。

  default constructor僅在編譯器需要它時,才會被合成出來。

   通常來說,由編譯器合成出來的default constructor是沒啥用的(trivial),但有以下幾種例外:

   (1)帶有“Default Constructor”的Member Class Object

   如果一個class沒有任何constructor,但它內含一個member object,而後者有default constructor,那麼編譯器會在constructor真正需要被調用時未此class合成一個“nontrivial”的default constructor.為了避免合成出多個default constructor,解決方案是把合成的default constructor、copy constructor、destructor、assignment copy operator都以inline方式完成。一個inline函數有靜態連結(static linkage),不會被檔案以外者看到。如果函數太複雜,不適合做成inline,就會合成出一個explicit non-inline static實體。

   根據準則“如果class A內含一個或一個以上的member class objects,那麼class A的每一個constructor必須調用每一個member classes的default constructor”,即便對於使用者明確定義的default constructor,編譯器也會對其進行擴張,在explicit user code之前按“member objects在class中的聲明次序”安插各個member所關聯的default constructor.

 class Dcpey   { public:Dopey(); ... };
class Sneezy  { public:Sneezy(int); Sneezy(); ... };
class Bashful { public:Bashful(); ... };

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

Snow_White::Snow_White() : sneezy(1024)
{
mumble = 2048;
}
// 編譯器擴張後的default constructor
Snow_White::Snow_White() : sneezy(1024)
{
// 插入member class object
// 調用其constructor
dopey.Dopey::Dopey();
sneezy.Sneezy::Sneezy();
bashful.Bashful::Bashful();

// explicit user code
mumble = 2048;
}

關鍵字: malloc wxWidgets OpenGL 多態性 doxygen 《深度探索C++物件模型》讀書筆記(2)。

   (2)“帶有Default Constructor”的Base Class

   如果一個沒有任何constructors的class派生自一個“帶有default constructor”的base class,那麼這個derived class的default constructor會被視為nontrivial,並因此需要被合成出來。

   需要注意的是,編譯器會將這些base class constructor安插在member object之前。

   (3)“帶有一個Virtual Function”的Class

   另有兩種情況,也需要合成出default constructor:

   (a)class聲明(或繼承)一個virtual function

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

   以下面這個程式片段為例:

 class Widge {
public:
virtual void flip() = 0;
// ...
};

void flip(const Widge& widge) { widge.flip(); }

// 假設Bell和Whistle都派生自Widge
void foo()
{
Bell b;
Whistle w;

flip(b);
flip(w);
}

   下面兩個擴張操作會在編譯期間發生:

   1.一個virtual function table會被編譯器產生出來,內放class的virtual functions地址;

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

   此外,widge.flip()的虛擬引發操作(virtual invocation)會被重新改寫,以使用widge的vptr和vtbl中的flip()條目。

 // widge.flip()的虛擬引發操作的轉變
(*widge.vptr[1])(&widge)

   為了讓這個機制發揮功效,編譯器必須為每一個Widge(或其衍生類別)之object的vptr設定初值,放置適當的virtual table地址。

關鍵字: malloc wxWidgets OpenGL 多態性 doxygen 《深度探索C++物件模型》讀書筆記(2)。

   (4)“帶有一個virtual Base Class”的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; };

// 無法在編譯時間期決定出pa->X::i的位置
void foo(const A* pa) { pa->i = 1024; }

main()
{
foo(new A);
foo(new C);
// ...
}

   編譯器需要在derived class object中為每一個virtual base classes安插一個指標,使得所有“經由reference或pointer來存取一個virtual base class”的操作可以通過相關指標完成。

 // 可能的編譯器轉變操作
// _vbcX表示編譯器所產生的指標,指向virtual base class X
void foo(const A* pa) ...{ pa->_vbcX->i = 1024; }

   小結:在合成的default constructor中,只有base class subobjects和member class objects會被初始化,所有其它的nonstatic data member都不會被初始化。

  有三種情況會以一個object的內容作為另一個class object的初值,即object賦值、object參數傳遞、object作為函數傳回值。

   如果class沒有提供一個explicit copy constructor,其內部是以所謂的default memberwise initialization手法完成的,也就是把每一個內建的或派生的data member(例如一個指標或一個數組)的值,從某個object拷貝一份到另一個object身上,不過它並不會拷貝其中的member class object,而是以遞迴的方式施行memberwise initialization. copy constructor僅在必要的時候(class不展現bitwise copy semantics)才由編譯器產生出來。

關鍵字: malloc wxWidgets OpenGL 多態性 doxygen 《深度探索C++物件模型》讀書筆記(2)。

   已知下面的class Word聲明:

 // 以下聲明展現了bitwise copy semantics
class Word {
public:
Word( const char* );
~Word(){ delete []str; }
// ...
private:
int cnt;
char *str;
};

   對於上述這種情況,並不需要合成出一個default copy constructor,因為上述聲明展現了“default copy semantics”。

   然而,如果class Word是這樣聲明:

 // 以下聲明未展現出bitwise copy semantics
class Word {
public:
Word( const String& );
~Word();
// ...
private:
int cnt;
String& str;
};

   在這種情況下,編譯器必須合成出一個copy constructor以便調用member class String object的copy constructor:

 // 一個被合成出來的copy constructor
inline Word::Word(const Word& wd)
{
str.String::String(wd.str);
cnt = wd.cnt;
}

   一個class不展現出“bitwise copy semantics”的四種情況:(1)當class內含一個member object而後者的class聲明有一個copy constructor時(無論是被明確聲明或被合成而得)

   (2)當class繼承自一個base class而後者存在有一個copy constructor時

   對於前兩種情況,編譯器必須將member或base class的“copy constructor叫用作業”安插到被合成的copy constructor中。

   (3)當class聲明了一個或多個virtual functions時

   由於編譯器要對每個新產生的class object的vptr設定初值,因此,當編譯器匯入一個vptr到class之中時,該class就不再展現bitwise semantics了。

關鍵字: malloc wxWidgets OpenGL 多態性 doxygen 《深度探索C++物件模型》讀書筆記(2)。

   當一個base class object以其derived class的object內容做初始化操作時,其vptr複製操作必須保證安全,而如果依舊採用bitwise copy的話,base class object的vptr會被設定指向derived class的virtual table,而這將導致災難。

   (4)當class派生自一個繼承串鏈,其中有一個或多個virtual base classes時當一個class object以其derived classes的某個object作為初值時,為了完成正確的virtual base class pointer/offset的初值設定,編譯器必須合成一個copy constructor,安插一些碼以設定virtual base class pointer/offset的初值,對每一個member執行必要的memberwise初始化操作,以及執行其他的記憶體相關操作。在這種情況下,簡單的bitwise copy所做的就遠遠不夠了。

   ***最佳化***

   (1)在使用者層面做最佳化定義一個“計算用”的constructor:

 X bar(const T &y,const T &z)
{
X xx;
// ... 以y和z來處理xx
return xx;
}

   上述constructor要求xx被“memberwise”地拷貝到編譯器所產生地_result之中。故可定義如下的constructor,可以直接計算xx的值:

 X bar(const T &y,const T &z)
{
return X(y,z);
}

   (2)在編譯器層面做最佳化比較以下三處初始化操作:

 X xx0(1024);
X xx1 = X(1024);
X xx2 = (X)1024;

   其中,xx0是被單一的constructor操作設定初值:

   xx0.X::X(1024);

   而xx1和xx2則是調用兩個constructor,產生一個暫時性的object並設以初值1024,接著將暫時性的object以拷貝建構的方式作為explicit object的初值,最後還針對該暫時性object調用class X的destructor:

關鍵字: malloc wxWidgets OpenGL 多態性 doxygen 《深度探索C++物件模型》讀書筆記(2)。

 X _temp0;
_temp0.X::X(1024);
xx1.X::X(_temp0);
_temp0.X::~X();

   由此可以看出,編譯器所做的最佳化可導致機器碼產生時有明顯的效率提升,缺點則是你不能安全地規劃你的copy constructor的副作用,必須視其執行而定。

   那麼,究竟要不要copy constructor?copy constructor的應用,迫使編譯器多多少少對你的程式碼做部分最佳化。尤其是當一個函數以傳值(by value)的方式傳回一個class object,而該class有一個copy constructor時,這將導致深奧的程式轉化。

   舉個簡單的例子,不管使用memcpy()或memset(),都只有在“classes不含任何由編譯器產生的內部members”時才能有效運行。而如下的class由於聲明了一個virtual function,編譯器為其產生了vptr,此時若使用上述函數將導致vptr的初值被改寫。

 class Shape ...{
public:
// 這會改變內部的vptr
Shape() { memset(this,0,sizeof(Shape)); };
virtual ~Shape();
// ...
};

// 擴張後的constructor
Shape::Shape()
{
// vptr必須在使用者的代碼執行之前先設定妥當
_vptr_Shape = _vtbl_Shape;

// memset會將vptr清為0
memset(this,0,sizeof(Shape));
}

   ***成員的初始化隊伍***

   下列情況中,為了讓你的程式能夠被順利編譯,你必須使用member initialization list:

   (1)當初始化一個reference member時;

   (2)當初始化一個const member時;

   (3)當調用一個base class的constructor,而它擁有一組參數時;

   (4)當調用一個member class的constructor,而它擁有一組參數時。

關鍵字: malloc wxWidgets OpenGL 多態性 doxygen 《深度探索C++物件模型》讀書筆記(2)。

   成員初始化列表有時可帶來巨大的效能提升,不妨看看下面一組對比:

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

// 其constructor可能的內部擴張結果
Word::Word()
{
// 調用String的default constructor
_name.String::String();

// 產生暫時性對象
String temp = String(0);

// “memberwise”地拷貝_name
_name.String::operator=(temp);

// 摧毀暫時性對象
temp.String::~String();

_cnt = 0;
}

   若使用成員初始化列表:

 // 較好的方式
Word::Word : _name(0)
{
_cnt = 0;
}

Word::Word()
{
// 調用String(int) constructor
_name.String::String(0);
_cnt = 0;
}

   需要注意的是,成員初始化列表中的項目是依據class中的member聲明次序,一一安插到explicit user code之前的。(因此,對於運算式兩邊均出現member的情形要特別小心)

   下面就給出錯誤執行個體:

 class X {
int i;
int j;
public:
// i比j先被賦值,而此時j尚未有初值
X(int val) : j(val),i(j)
{ }
...
};

  系列文章:

  《深度探索C++物件模型》讀書筆記(1)

  《深度探索C++物件模型》讀書筆記(3)

 

  《深度探索C++物件模型》讀書筆記(4)

  《深度探索C++物件模型》讀書筆記(5)

  《深度探索C++物件模型》讀書筆記(6)

  《深度探索C++物件模型》讀書筆記(7)

  《深度探索C++物件模型》讀書筆記 最後一記

 

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.