template
宣告
? 當我們宣告一個template class、template class memberfunction等,會發生何事?
? 現有如下片段:
template <class Type>
class Point
{
public:
enum Status{ unallocated, normalized };
Point( Type x = 0.0, Type y = 0.0, Type z = 0.0 );
~Point();
void* operator new( size_t );
void operator delete( void*, size_t );
//...
private:
static Point<Type> *freeList;
static int chunkSize;
Type _x, _y, _z;
}
? 宣告一個template class,在程式中編譯器對其并沒有任何反應,換句話說,上述的data member其實并不可用
實體化
- 我們需要顯示地指定型別才可使用data member:
Point::Status s;
Point<float>::freeList;
//如下會產生第二個freeList實體
Point<double>::freeList;
//定義指標指向特定實體,程式中啥也沒發生,因為編譯器不需要知道與該class有關的任何member資料,也沒必要初始化template實體,且指標可以為0
Point< float >* ptr = 0;
//reference則不同,它需要實體化,因為reference是需要系結物件的
const Point<float>& ref = 0;
//擴展
Point<float> temp( (float) 0 );
const Point<float>& ref = temp;
//導致實體化
const Point<float> origin;
? 對于int和long相同的架構中,以下兩個實體化c++standard并未對此進行強制規定應實體化一個還是兩,不過大部分編譯器都是實體化兩個:
Point<int> pi;
Point<long> pi;
- 目前的編譯器,面對一個template的處理是完全決議但不做型別檢驗;只有在實體化操作發生時才做型別檢驗
- template class內的member functions只有在被使用的時候,才會被實體化
名稱決議
-
在c++standard規定了template兩個不一樣的端,分別是定義template(template定義的檔案)的程式端和實體化template(使用的特定例子)的程式端
//定義template端 //只有一個foo()宣告位于定義端內 extern double foo( double ); template< class type > class A { public: void do1() { _member = foo( _val ); } type do2() { return foo( _member ); } //... private: int _val; type _member; } //實體化端 //兩個foo()宣告在實體化端內 extern int foo( int ); template< class type > class A { public: void do1() { _member = foo( _val ); } type do2() { return foo( _member ); } //... private: int _val; type _member; } A<int> a; //應該呼叫extern double foo( double ),因為_val的型別與template type引數型別無關 a.do1(); //應呼叫extern int foo( int ),因為_member與template type引數型別有關 a.do2(); -
template中,對一個nonmenber name的決議結果是根據此name的使用是否與"用以實體化該template的引數型別"有關來決定:
- 若其使用互不相關,則使用定義端來決定name
- 若其使用有關聯,則使用實體化端來決定name
-
編譯器必須保持兩個端背景關系(scope contexts):
- 定義端,專注一般的template class
- 實體化端,專注特定實體
例外處理
-
想要支持例外處理,編譯器的主要作業是找出catch子句以處理被拋出來的exception
-
例外處理由三個主要組件構成:
- 一個throw子句,它再程式某處發出一個exception,被拋出的exception可為內建型別,亦可為自定型別
- 一個或多個catch子句,每個catch子句都為一個exception handler,表示此子句準備處理某種型別的exception,且在封閉的大括號區段中提供實際的處理程式
- 一個try區段,他被圍繞一系列敘述句,這些敘述句可能會引發catch子句起作用
-
當一個exception被拋出,控制權會從函式呼叫中被釋放出,并尋找一個吻合的catch子句,若沒有則呼叫默認處理例程terminate(),當控制權被釋放,堆疊中每個函式呼叫也就被推離,被推離前函式的local class objects的destructor會被呼叫
Point* mumble { //區域一 Point* pt1, * pt2; //若一個exception于此被拋出,mumble()會被推出堆疊 pt1 = foo(); if( !pt1 ) return 0; //區域二 Point p; //若一個exception于此被拋出,在推出mumble()前需要先呼叫p的destructor pt2 = foo(); if( !pt2 ) return pt1; }- 支持以上exception handling,編譯器有兩種策略:
- 把兩個區域(上面兩個exception)以個別的"將被摧毀的local objects"鏈表(在編譯器準備妥當)聯合起來
- 讓兩個區域共享同一個鏈表,該鏈表在執行期擴大或縮小
- 一個函式可以分為多個區段:
- try以外的區域,且沒有active local objects
- try以外的區域,但有一個以上的active local objects需要析構
- try以內的區域
- 支持以上exception handling,編譯器有兩種策略:
? 現有一函式含有對一塊共享記憶體的locking和unlocking操作,此時例外處理不保證能正確運行:
void mumble( void* arena )
{
Point* p = new Point;
smLock( arena );
//若此處有exception被拋出,會產生問題
//...
smUnLock( arena );
delete p;
}
//因此我們需要安插default catch子句
void mumble( void* arena )
{
Point* p = new Point;
smLock( arena );
try
{
smLock( arena );
}
catch(...)
{
smUnLock( arena );
delete p;
throw;
}
smUnLock( arena );
delete p;
}
- 處理資源管理,一個辦法是將資源需求封裝于class object內,并由destructor釋放資源
? 當一個exception發生時,編譯系統必須完成以下:
- 檢驗發生throw操作的函式
- 決定throw操作是否發生在try區段
- 若是,編譯系統必須把exception type拿來和每個catch子句比較
- 若比較后吻合,流程控制交給catch子句
- 若throw的發生不在try區段,或沒有一個catch子句吻合,那么系統必須摧毀所有active local objects,從堆疊中將目前的函式推離,到下一個函式,再重復2-5
-
編譯器必須標示出之前所提到的多個區段,并使它們對執行期的例外處理有所作用,有一個策略是構造program counter-range表格:
program counter內含下一個即將執行的程式指令,為了在一個內含try區段的函式中標示出某區域,可以把program counter的起始值和結束值存盤在范圍表格
當throw發生時,目前的program counter值拿來與對應的范圍表格進行對比,以決定目前作用中的區域是否在一個try區段內,若是,則需找出相關catch子句;若無法處理或exception再次被拋出,目前此函式會從堆疊中被推出,而program counter會被設定為呼叫端地址,回圈將再度開始
-
對于被拋出的exception,編譯器必須產生一個型別描述器,對exception的型別進行編碼,若為一個derived type,編碼內容必須包含其所有base class型別資訊(可能被member function捕捉),編譯器還必須為每一個catch子句產生一個型別描述器,執行期的handler會將"被拋出的object的型別描述器"和"每一個catch子句的型別描述器"進行比較,直到吻合或是堆疊已被推離
執行期型別識別
-
downcast有效地把一個base class轉換為繼承結構的derived class,但其有潛在的危險,因為它遏制了型別系統的作用
-
一個type-safe downcast必須在執行期對指標查詢,看其是否指向它所表達的object的真正型別,想要支持type-safe downcast,需要以下要求:
- 需要額外的空間存盤型別資訊,通常為指標
- 需要額外的時間來決定執行期的型別
-
c++的RTTI機制提供安全的downcast設備,但只對多型型別有效
-
dynamic_cast可以在執行期決定真正的型別
-
type-safe dynamic_cast:
- 若downcast為安全的,則會傳回被適當轉換過的指標;若不安全,則傳回0
- 對一個class指標施行dynamic_cast,會獲得true或flase
- 若傳回真正的地址,則表示這一object的動態型別被確定,一些與型別有關的操作可以施行于此
- 若傳回0,則表示沒有指向任何object,應該以另一種邏輯施行于未確定的object
- 對于reference dynamic_cast同樣適用,但若為non-type-safe cast,結果和指標不同,因為reference不可以把自己設為0,因此會有另一套方案:
- 若reference真正參考到適當的derived class,downcast被執行
- 若reference并不真正是某種derived class,由于不能傳回0,則拋出bad_cast exception
-
dynamic_cast的成本:編譯器產出型別描述器
//fct為type派生 typedef type* ptype; typedef fct* pfct; do( ptype pt ) { if( pfct pf = dynamic_cast<pfct>(pt) ) { //... } //轉換 //virtual table第一個slot內含type_info object地址 ((type_info*)(pt->vptr[0]))->_type_descriptor }
轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/522844.html
標籤:C++
