深入探索C++對象模型之七 --- 站在對象模型的尖端

深入探索C++對象模型之七 — 站在對象模型的尖端

template instantiation 模版具現化
template、exception handling和runtime type idetification

Template

模版只是定義了函數

tempalte的聲明

當我們聲明template的時候,編譯器對於聲明代碼是不做任何處理的,只有在程序中使用到了具體類型的類或者函數的時候,編譯器纔會根據模版定義具現化該類型參數的class和member function。編譯器會根據出現的參數類型instantiation一個具體的實體。

  1. 如果在程序中使用到模版類中的static data member。必須指定patter,如:

    template <class T>
    class Point {
        ...
    private:
        static int value;
    }

    我們如果想訪問value的話必須Point<int>::value或者Point<double>::value, 而不能是Point::value.這會使得編譯器產生針對int或者double的instantiation實體。否則的話編譯器不做任何處理自然也不存在所謂的static data member實體。
    其次,我們如果使用point的話是不會產生instantiation實體的,如Point<float> *ptr=0,這的確是一個Point<float>指針,但是這個不是一個Point<float> class object,不關心該object的內部佈局。而使用refenece的話則必須要instantiation實體,因爲reference是class object的引用,因此必須要產生instantiation實體。

  2. member function也不應該全部被“實體”化,只有在member functions被使用的時候纔會被instantiation。試想如果class中有100個member functions,但是只用到了兩三個,那麼如果實體化全部的函數將會花費大量的時間和空間。

  3. 那麼這些member function是在什麼時候“具現”出來的呢?要麼在編譯時期,要麼則是在鏈接時期。

  4. 所有與類型有關的校驗,如果牽涉到template參數都必須延遲到真正的具現操作時才發生。

  5. Template中的名稱決議方式。一個編譯器必須保持兩個scope context
    5.1 scope of template declaration 用於專注於一般的template class。當名稱的決議和template參數類型無關的時候就使用該scope
    5.2 scope of template instantiation 用於專注於特定的實體。當名稱的決議和template參數類型相關的時候則使用該scope

  6. Member function的具現行爲

template的特化和偏特化(template specialization and partial specialization)

對於之前提到的template instantiation,在編譯時期編譯器可以根據你傳入的模版參數instantiation實體,並且具現化class member function。但是有時候真對某些類型可能member function的具體行爲我們需要重新定義,那麼這時候就需要額外限定模版的參數,指定當這種參數類型的時候使用自己特別定製的member function,這就是特化。

而如果template我們只是指定部分的類型那麼這就是偏特化。

如下例子:

#include<iostream>
using namespace std;

class man {
    private:
        string name;
        int data;
    public:
        man(string s, int i):name(s),data(i) {
        }
        void show() const {
            cout << "this name is " << name << ", data = " << data << endl;
        }
        void setData(int d) {
            data = d;
        }
        int getData() const {
            return data;
        }
};

template <class T>
void mSwap(T &t1, T &t2) {
    T temp = t1;
    t1 = t2;
    t2 = temp;
    cout << "Here are template version " << t1 << "and" << t2 << endl;
};

int main() {
    man m1("aa", 99);
    man m2("bb", 100);
    mSwap(m1, m2);
}

上面的代碼編譯失敗是因爲編譯器會instantiation一個man的實體,但是man類型沒有定義<<運算符,因此在mSwap最後一行就會編譯報錯,因此我們就需要針對man這種特別定製mSwap函數,如下:

tempalte <>
void mSwap(man &m1, man &m2) {
    /*do something*/
}

如果有非模板函數存在,當匹配度相同的時候優先使用非模板函數。函數模板定製是實例化模板,而不是模板的重載。

異常處理

c++程序中exception handling主要有三個語彙構成:(1)一個thow子句 (2)一個或者多個catch子句 (3)一個try區段

當一個exception被丟出去時,控制權會從函數調用中釋放出來,並尋找一個吻合的catch子句,如果找不到的話就使用默認的處理例程terminate().當控制權被放棄後,堆棧中的每一個函數調用都將被推離,這個程序稱爲unwinding the stack。在每一個函數被推離堆棧之前,函數的local class object的destructor會被調用。

對於在函數內部各個區段間產生的exception,到底是否已經產生local class object以及是否需要釋放資源這些都是編譯器乾的事。

對於每一個被丟出來的exception,編譯器都必須產生一個類型描述器,同時編譯器還要爲每一個catch子句產生一個類型描述器。執行期間exception handle就將這兩個描述器進行比較直到找到一個吻合的catch子句處理,或者直到堆棧已經被“unwound”而terminate()已被調用。

如果在catch語句段中再次throw出exception object的話,拋出的仍然是原來一開始的object class,即使在catch中做過處理,catch子句對於exception object的任何改變都是局部性的。

執行期類型識別 RTTI

dynamic_cast可以在執行期決定真正的類型,如果downcast是安全的那麼這個運算符會傳回適當轉型過的指針,而如果downcast是不安全的那麼這個運算符傳回0。那麼dynamic_cast的成本是什麼呢?

type_info是C++所定義的類型描述器的class名稱,該class中放置着需要的類型信息。virtual table中第一個slot內含type_info object的地址。當需要判斷時,這兩個比較器會被交給一個runtime library函數比較,告訴我們是否吻合。

程序執行中對一個class指針類型施以dynamic_cast運算符的話,會獲得true或false

  • 如果傳回真正的地址,表示這個object的動態類型被確認了,一些類型有關的操作可以施行於其上
  • 如果傳回0,表示沒有指向任何object,意味着應該以另外一種邏輯施行於這個動態類型未確定的object上

而如果對一個reference施行於dynamic_cast運算符的話,會是另外一個結果:

  • 如果reference參考適當的derived class,那麼downcast會被執行而程序可以繼續進行
  • 如果reference並不真正是一種derived class,那麼會丟出一個bad_cast exception。

typeid運算符傳回一個const reference,類型是type_info。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章