C++物件模型——"無繼承"情況下的物件建構(第五章)

來源:互聯網
上載者:User

標籤:

5.1 "無繼承"情況下的物件建構    考慮下面這個程式片段:
1Point global;23Point foobar()4{5Point local;6Point *heap = new Point;7*heap = local;8// ... stuff ...9delete heap;10return local;11}
L1,L5,L6表現出三種不同的對象產生方式:global記憶體配置,local記憶體配置和heap記憶體配置.L7把一個 class object指定給另一個,L10設定返回值,L9則明確地以 delete 運算子刪除heap object.
    一個object的生命是,該object的一個執行循環,local object的生命從L5的定義開始,到L10為止.global object的生命和整個程式的生命相同.heap object的生命從它被 new 運算子配置出來開始,到它被 delete 運算子摧毀為止.
    下面是Point的第一次聲明,可以寫成C程式.C++ Standard說這是一種所謂的Plain Of Data聲明形式:
typedef struct {float x, y, z;} Point;
如果以C++來編譯這段碼,會發生什麼事情?觀念上,編譯器會為Point聲明一個trivial default constructor,一個trivial destructor,一個trivial copy constructor,以及一個trivial copy assignment operator.但實際上編譯器會分析整個聲明,並為它貼上Plain Of Data標籤.
    當編譯器遇到這樣的定義:
1 Point global;
    時,觀念上Point的trivial constructor和destructor都會被產生並被調用, constructor在程式起始(startup)處被調用而destructor在程式的exit()處被調用(exit()由系統產生,放在main()結束之前).然而,事實上那些trivial members要不是沒被定義,就是沒被調用,程式的行為一如它在C中的表現一樣.
    在C中,global被視為一個"臨時性的定義",因為它沒有明確的初始化操作,一個"臨時性的定義"可以在程式中發生多次.那些執行個體會被連結器摺疊起來,只留下單獨一個實體,被放在程式data segment中一個"特別保留給為初始化的global objects使用"的空間.
    C++並不支援"臨時性的定義",這是因為 class 構造行為的隱含應用的緣故. global在C++中被視為完全定義.C++中所有全域對象都被當作"初始化過的資料"來對待.
    foobar()函數中的L5,有一個Point object local,同樣也是既沒有被構造也沒有被解構. Point object local如果沒有先經過初始化,可能會成為潛在的程式bug——萬一第一次使用它就需要其初始值的話(如L7).至於heap object在L6的初始化操作:
Point *heap = new Point;
    會被轉換為對 new 運算子(由library提供)的調用:
Point *heap = __new(sizeof(Point));
    再一次強調,並沒有default constructor施行於 new 運算子所傳回的Point object上.L7對此object有一個賦值(賦值,assign)操作,如果local曾被適當地初始化過,一切就沒有問題.
7 *heap = local;
     事實上這一行會產生編譯器警告如下:
warning,line 7: local is used before being initialized.
     觀念上,這樣的指定操作會觸發trivial copy assignment operator進行拷貝搬運操作.然而實際上此object是一個Plain Of Data,所以賦值操作(assignment)將只是像C那樣的純粹位搬運操作,L9執行一個 delete 操作:
9 delete heap;
    會被轉換為對 delete 運算子(由libraray提供)的調用:
__delete(heap);
    觀念上,這樣的操作會觸發Point的trivial destructor.但destructor要不是沒有被產生就是沒有被調用.最後,函數以傳值(by value)的方式將local當作返回值傳回,這在觀念上會觸發trivial copy constructor,不過實際上 extern 操作只是一個簡單的位拷貝操作,因為對象是一個Plain Of Data.

抽象資料類型 (Abstract Data Type)    以下是Point的第二次聲明,在 public 介面下多了 private 資料,提供完整的封裝性,但沒有提供任何 virtual function:
class Point {public:Point(float x = 0.0, float y = 0.0 float z = 0.0): _x(x), _y(y), _z(z) {}// no copy constructor, copy operator or destructor defined ...private:float _x, _y, _z;};
這個經過封裝的Point class, 其大小並沒有改變,還是三個連續的float,是的,不論 private,public 存取層,或是member function的聲明,都不會佔用額外的對象空間.
    沒有為Point定義一個copy constructor或copy operator,因為預設的位語意(default bitwise semantics,第二章51頁) 已經足夠了.也不需要提供一個destructor,因為程式預設的記憶體管理方法也足夠.
    對於一個global實體:
Point global;// 實施Point::Point(0.0, 0.0, 0.0);
    現在有了default constructor作用於其上,由於global被定義在全域範疇中,其初始化操作將延遲到程式啟用(startup)時才開始(詳見6.1節)
    如果要對 class 中的所有成員都設定常量初值,那麼 給予一個 explicit initialization list會比較高效.甚至在local scope中也是如此.舉個例子,考慮下面這個程式片段:
void mumble() {Point local1 = {1.0, 1.0, 1.0};Point local2;// 相當於一個inline expansion// explicit initialization 會稍微快一些local2._x = 1.0;local2._y = 1.0;local2._z = 1.0;}
local1的初始化操作會比local2的高效,因為當函數的activation recored被放進程式堆棧時,上述initialization list中的常量就可以被放進local1記憶體中.
    explicit initialization list帶來三項缺點:
    1.只有當 class members都是 public 時,此方法才奏效.
    2.只能指定常量,因為它們在編譯時間期就可以被評估求值.
    3.由於編譯器並沒有自動施行,所以初始化行為的失敗可能會比較高一些.
    explicit initialization list所帶來的效率優點能夠補償其軟體工程上的缺點嗎?
    一般而言,答案是no.然而在某些特殊情況下又不一樣.例如,或許以手工打造了一些巨大的資料結構如調色盤,或者正要把一堆常量資料傾倒給程式,那麼 explicit initialization list的效率會比 inline constructor好的多,特別是對全域對象而言.
    在編譯器層面,會有一個最佳化機制用來識別 inline constructor,後者簡單地提供一個member-by-member的常量指定操作,然後編譯器會抽取那些值,並且對待它們就好像是 explicit initialization list所供應的一樣,而不會把constructor擴充成一系列的assignment指令.
    於是,local Point object的定義:
{Point local;}
現在被附加上default Point constructor的 inline expansion:
{// inline expansion of default constructorPoint local;local._x = 0.0;local._y = 0.0;local._z = 0.0;}
L6配置出一個heap Point object:
6Point *heap = new Point;
    現在則被附加一個"對default Point constructor的有條件叫用作業":
Point *heap = __new(sizeof(Point));if (heap != 0)heap->Point::Point();
然後才又被編譯器進行 inline expansion操作,至於把heap指標指向local object:
7*heap = local;
    則保持這簡單的位拷貝操作,以傳值方式傳回local object,情況也是一樣:
10return local;
    L9刪除heap所指的對象:
9delete heap;
    該操作並不會導致destructor被調用,因為並沒有明確地提供一個destructor函數實體.
    觀念上,Point class 有一個相關的default copy constructor,copy operator和destructor,然而它們都是無關緊要的,而且編譯器實際上根本沒有產生它們.

為繼承做準備     第三個Point聲明,將為"繼承性質"以及某些操作的動態決議(dynamic resolution)做準備,當限制對z成員進行存取操作:
class Point {public:Point(float x = 0.0, float y = 0.0) : _x(x), _y(y) {}// no destructor, copy constructor, or copy operator defined ...virtual float z();protected:float _x, _y;};
再一次強調,並沒有定義一個copy constructor,copy operator,destructor.所有的members都以數值來存取,因此在程式層面的預設語意的下,行為良好.
    virtual functions的匯入促使每一個Point object擁有一個 virtual table Pointer.這個指標提供 virtual 介面的彈性, 代價是:每一個object需要一個額外的一個word空間.有什麼重大影響嗎?視應用情況而定!必須視它對多態設計所帶來的實際效益的比例而定.
    除了每一個 class object多負擔一個vptr之外,virtual function的引入也引發編譯器對Point class 產生膨脹作用:
    定義的constructor被附加了一些碼,以便將vptr初始化.這些碼必須被附加在任何base class constructor的調用之後,但必須在任何由使用者(程式員)供應的代碼之前.例如,下面就是可能的附加結果:
Point *Point::Point(Point *this, float x, float y) : _x(x), _y(y) {// 設定object的virtual table pointer(vptr)this->__vptr_point = __vtbl__point;// 擴充member initialization listthis->_x = x;this->_y = y;// 傳回this對象return this;}
合成一個copy constructor和一個copy assignment operator,而且其操作不再是trivial(但implicit destructor仍然是trivial).如果一個Point object被初始化或以一個derived class object賦值,那麼以位為基礎(bitwise)操作可能給vptr帶來非法設定.
// copy constructor的內部合成inline Point* Point::Point(Point *this, const Point &rhs) {// 設定object的virtual table pointer(vptr)this->__vptr_Point = __vtbl__Point;// 將rhs座標中的連續位拷貝到this對象,或是經由member assignment提供一個member ...return this;}
編譯器在最佳化狀態可能會把object的連續內容拷貝到另一個object上,而不會實現一個精確地"以成員為基礎"的賦值操作.C++ Standard要求編譯器盡量延遲nontrivial memebrs的實際合成操作,直到真正遇到其使用場合為止.
1Point global;23Point foobar()4{5Point local;6Point *heap = new Point;7*heap = local;8// ... stuff ...9delete heap;10return local;11}
L1的global初始化操作,L6的heap初始化操作以及L9的heap刪除操作,都還是和稍早的Point版本相同,然而L7的memberwise賦值操作:
*heap = local;
    很可能觸發copy assignment operator的合成,以及其叫用作業的一個 inline expansion(內部擴充);以 this 取代heap而以rhs取代local.


著作權聲明:本文為博主原創文章,未經博主允許不得轉載。

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.