讀【深度探索C++物件模型】【下】

來源:互聯網
上載者:User

【Template】
Template的出現大大改變了C++的編程方式,甚至在傳統的物件導向編程方式的基礎上派生出了泛型程式設計方式。簡單的理解泛型,可以看成是以平行層級的類(相對於繼承來說)對代碼依照演算法邏輯進行複用。比如有一個template <class Type> A。當你分別使用A<int>, A<double>, A<aClass &>對其具現化的時候。可以簡單的看成編譯器為你產生了三個類A_int, A_double, A_aClassR(名字是虛擬)。這三個類的演算法邏輯完全一致,只是其中核心資料類型(即以Type標識的類型)不一樣。這種重用方式,擺脫了傳統的通過繼承了實現的弊端,增加了重用的簡單性降低了耦合性。

但是,泛型程式設計可能會造成代碼的膨脹。比如你A類中有50個方法(資料成員若干),你分別用10種類型對其進行具現化,編譯器就需要產生10個帶有50個方法的類,大大浪費了空間。因此,為了避免這個問題,編譯器採取了很多的最佳化手段。比如,在具現化的過程中,產生的具體類只擁有用到過的方法,其他的都不會被插入到該具現的類中。再比如,對於不同的指標類型(A<class1 *>, A<class2 *>),其儲存方式和運行特點都一樣,編譯器可能會對所有指標類型產生一個類,這樣也能夠避免代碼膨脹現象。

 
【異常處理】

結構化異常機制(try...catch)的引入對C++的影響是巨大的。一方面它使得C++的錯誤處理變得清晰而統一;另一方面人們會很顧慮異常處理的引用會在時空兩方面影響程式效能。從編譯器來看,異常處理的引入對其影響很大,它需要增加結構對程式進行分段,保證異常處理能夠進行。看下面這段代碼:

 void Test()
{
 A *a1;
 A a2;

 try
 {
  ...
 }
 catch(...)
 {
  ...
  throw;
 }
}

這個程式被分成三個區段,A *a1是一個,A a2是另外一個,try中部分是第三個。當try中某句代碼執行發生異常時,需要摧毀該函數的棧資訊。對於A *a1來說只需要簡單的釋放空間就好;而對於A a2來說不僅要釋放空間還要調用A的解構函式,執行析構處理。 而對這個拋出異常而言,首先需要判斷它屬於哪個區段(方法可以很多,有一種被稱為program counter的)。如果它不屬於某個try區段,將繼續被拋出;當它屬於某個try區段時,異常將與catch的內容比較。如果不存在匹配的catch,異常將被繼續拋出;否則,執行catch中的內容。

如果這個catch段不能完全處理該異常,我們通常的做法是用throw將其繼續拋出,為了保證異常中包含的資訊可以正確跟蹤和理解,重新拋出的異常捨棄所有對它的操作,保持原始的模樣(如果想改變,可以拋出另外一個異常,而不是繼續拋出捕獲的異常)。

從上面的描述來看,異常處理的引入會增加時間和空間的負擔(因為要將代碼分段)。這種負擔不表現在異常發生的時候(事實上,這部分負擔往往不需要太在意,因為異常不總是會發生的),而是表現在程式正常執行時。書中的資料顯示,在任何編譯器上,異常的引入都會使程式體積增加(5%左右)。並且在不觸發異常的情況下,執行時間增加(也是5%吧)。

但通常,這種代價與異常處理帶來的價值相比是可以接受的。對於我們來說,也許應該考慮的,不是是否使用異常處理(個人觀點是除非對時空有著苛刻的要求,都最好使用),更值得考慮的是如何更好的使用異常處理。大量的C++實踐書籍中都會提到該問題,在任何語言中,這都是一個很有藝術性的設計問題。

 

【RTTI】

 RTTI就是運行時的類型識別。它是類型比較(比如異常處理中catch的類型和異常的類型比較)和安全向下轉型(由基類向衍生類別轉型)。RTTI的實現方法有很多,C++常用的一種手段是將類型資訊安插在虛表的第一個slot內(在前面那副圖裡可以看到)。這個類型資訊包括本身和基類的資訊。

很顯然,由於RTTI的引入,在一個繼承體系內,無論是否存在虛方法,都需要額外付出一個虛指標和虛表的代價。同時,在向下轉型時,由於需要判斷類型,也沒有原來那麼高效。但和看待異常處理一樣,我們要看到它帶來的好處通常是大於坏處的。RTTI式類型比較(沒有它幾乎沒辦法實現異常處理)和安全轉型變成了可能,這些付出也算是有所回報了吧。

【虛繼承】

引入虛繼承,是為了避免在多繼承中出現多個父類派生自同一個祖先,以至於其資料重複,很難操作。如下面這個虛繼承體系,Vectex和Point3d有共同的父類Point2d,而Vectex3d多繼承自Vectex和Point3d。這時候,就需要利用虛繼承,使得共同基類的資料被所有虛繼承自其的子類共用。顯然,這種共用關係,使得資料在存放上無法保證繼承體系中的每個類的資料都是挨著的,如果還採取普通多繼承中的資料存放方式,就使得在類型轉換的時候無法正確擷取資料。

class Point2d
{
public:
 // ...
protected:
 float _x, _y;
};

class Vertex :
 public virtual Point2d
{
public:
 // ...
protected:
 Vertex *next;
};

class Point3d :
 public virtual Point2d
{
public:
 // ...
protected:
 float _z;
};

class Vertex3d :
 public Vertex, public Point3d
{
public:
 // ...
protected:
 float mumble;
};

為了保證轉型的正常進行,就需要在存放中安插一些輔助資料,來維繫這種動態性。通常所有的輔助資料包括指標和位移量。這是代表了兩種風格的設計。採用指標,是一種加層的思想,在兩個很難聯絡的東西之間添加一層,往往能起到很好的效果;而利用位移量,是一種利用計算來節省空間的、減少間接性的做法。在很多時候採用它可以大大減少演算法的空間佔用。對於我們來說,應該好好的掌握這兩種設計手段。而對於這個問題而言,不同的編譯器的實現各有千秋,其效果也是各具特色,希望仔細瞭解的人可參考原書中的敘述。

但無論哪一種方法,但難以避免的增加時間和空間的複雜度。比如函數:

void Point3d::operator += (const Point3d &rhs)
{
 _x += rhs._x;
 _y += rhs._y;
 _z += rhs._z;
}

經由編譯器處理後(指標方式),其中實現內容可能變成了如下這個樣子:

__vbcPoint2d->_x += rhs.__vbcPoint2d->_x;
__vbcPoint2d->_y += rhs.__vbcPoint2d->_y;
_z += rhs._z;

可以看到,虛繼承的引入導致了存取的間接性。因此,對虛繼承最好的應用就是,不在虛基類中放置任何資料(就像C#中介面所做的事情一樣)。這一點,需要在我們設計程式中好好把握。

此外,在虛繼承體系下,建構函式的處理與前面所敘述的也有所不同。為了避免繼承與同一個祖先的基類重複調用其建構函式,需要添加一些資訊,保證共同的祖先的建構函式只被調用一次。上面例子中的Point3d的建構函式有可能被擴充成這個樣子Point3d* Point3d::Point3d(Point3d* this, bool __most_derived, float x, float y, float z)。注意__most_derived這個變數。它只有在最子類(就是顯性調用的那個)的建構函式調用中才被設為true,在其基類中調用都會被設為false。比如,當程式中調用Vertex3d v(x, y, z)時,相當於調用Vertex3d(&v, true, x, y, z),並且該建構函式可能被展開成如下樣子:

 Vertex3d* Vertex3d::Vertex3d(Vertex3d *this, bool __most_derived, float x, float y, float z)
{
 if(__most_derived != false)
  this->Point::Point(x, y);

 this->Point3d::Point3d(false, x, y, z);
 this->Vertex::Vertex(false, x, y);

 // ...
}

其中,Point::Point只被調用一次。由於調用Point3d::Point3d和Vertex::Vertex時,__most_derived值均為false,因此在其中不會在調用Point::Point。這樣不但保證了所有基類的建構函式有且僅有被調用一次,還保證了調用的順序。

 

【結束語】

終於寫完了這本書的筆記。讀的比較細緻,所以寫的也比較多。知其然有時候也要知其所以然,這樣在編程時才能更加的胸有成足。總體上寫的比較boring,爭取下次寫的時候開心一點:)。

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在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.