深入探索C++對象模型之七 — 站在對象模型的尖端
template instantiation 模版具現化
template、exception handling和runtime type idetification
Template
模版只是定義了函數
tempalte的聲明
當我們聲明template的時候,編譯器對於聲明代碼是不做任何處理的,只有在程序中使用到了具體類型的類或者函數的時候,編譯器纔會根據模版定義具現化該類型參數的class和member function。編譯器會根據出現的參數類型instantiation一個具體的實體。
如果在程序中使用到模版類中的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實體。member function也不應該全部被“實體”化,只有在member functions被使用的時候纔會被instantiation。試想如果class中有100個member functions,但是只用到了兩三個,那麼如果實體化全部的函數將會花費大量的時間和空間。
那麼這些member function是在什麼時候“具現”出來的呢?要麼在編譯時期,要麼則是在鏈接時期。
所有與類型有關的校驗,如果牽涉到template參數都必須延遲到真正的具現操作時才發生。
Template中的名稱決議方式。一個編譯器必須保持兩個scope context
5.1 scope of template declaration 用於專注於一般的template class。當名稱的決議和template參數類型無關的時候就使用該scope
5.2 scope of template instantiation 用於專注於特定的實體。當名稱的決議和template參數類型相關的時候則使用該scopeMember 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。