日韩av黄I国产麻豆传媒I国产91av视频在线观看I日韩一区二区三区在线看I美女国产在线I麻豆视频国产在线观看I成人黄色短片

歡迎訪問 生活随笔!

生活随笔

當(dāng)前位置: 首頁 >

C++对象模型学习——站在对象模型的尖端

發(fā)布時(shí)間:2025/6/15 63 豆豆
生活随笔 收集整理的這篇文章主要介紹了 C++对象模型学习——站在对象模型的尖端 小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.

2019獨(dú)角獸企業(yè)重金招聘Python工程師標(biāo)準(zhǔn)>>>

?????? 這里要討論三個(gè)著名的C++語言擴(kuò)充性質(zhì),它們都會(huì)影響C++對(duì)象。它們分別是template、

exception handling(EH)和runtime type identification(RTTI)。

一、Template

???? C++程序設(shè)計(jì)的風(fēng)格及習(xí)慣,自從1991年的cfront 3.0引入tempaltes之后就深深地改變了。原

本template被視為對(duì)container classes如Lists和Arrays的一項(xiàng)支持,但現(xiàn)在它已經(jīng)成為標(biāo)準(zhǔn)模板庫

(也就是Standard Template Library,STL)的基礎(chǔ)。它也被用于屬性混合(如內(nèi)存配置策略或互

斥(mutual exclusion)機(jī)制的參數(shù)技術(shù)之中。它甚至被用于一項(xiàng)所謂的template metaprogram技

術(shù):class expression templates將在編譯時(shí)期而非執(zhí)行期被評(píng)估,因而帶來重大的效率提升)。

????? 下面是template的三個(gè)主要討論方向:

???? 1)template的聲明。基本來說就是當(dāng)聲明一個(gè)template class、template class member

function等待時(shí),會(huì)發(fā)生什么事情。

???? 2)如何”實(shí)例化(instantiates)“class object、inline nonmember以及member template

functions。這些是”每一個(gè)編譯單位都會(huì)擁有一份實(shí)例“的東西。

???? 3)如何”實(shí)例化(instantiates)“nonmember、member tempalte functions以及static

template class members。這些都是”每一個(gè)可執(zhí)行文件中只需要一份實(shí)例“的東西。這就是一般

而言template所帶來的問題。

???? 這里使用”實(shí)例化“(instantiation)這個(gè)字眼來表示”進(jìn)程(process)將真正的類型和表達(dá)式

綁定到template相關(guān)形式參數(shù)(formal parameters)上頭“的操作。舉個(gè)例子,下面是一個(gè)

template function:

template <class Type> Type min( const Type &t1, const Type &t2 ) { ... }

????? 用法如下:

min( 1.0, 2.0 );

???? 于是進(jìn)程把Type綁定為double并產(chǎn)生min()的一個(gè)程序文字實(shí)例(并施以”mangling“方法,給

它一個(gè)獨(dú)一無二的名稱),其中t1和t2的類型都是double。

???? 1、Template的”實(shí)例化“行為(Template instantiation)

?????? 考慮下面的template Point class:

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; };

???? 首先,當(dāng)編譯器看到template class聲明時(shí),它會(huì)做出什么反應(yīng)?在實(shí)際程序中,什么反應(yīng)也

沒有!也就是說,上述的static data members并不可用。nested enum或其enumerators也一

樣。

???? 雖然enum Status的真正類型在所有的Point instantiations中都一樣,其enumerators也是,

但它們每一個(gè)都只能夠通過template Point class的某個(gè)實(shí)例來存取或操作。因此我們可以這樣

寫:

// ok: Point<float>::Status s;

????? 但不能這樣寫:

// error: Point::Status s;

????? 即使兩種類型抽象地來說是一樣的(而且,最理想的情況下,我們希望這個(gè)enum只有一個(gè)

實(shí)例被產(chǎn)生出來。如果不是這樣,我們可能會(huì)想要把這個(gè)enum抽出到一個(gè)nontemplate base

class中,以避免多份拷貝)。

????? 同樣道理,freeList和chunkSize對(duì)程序而言也還不可用。我們不能夠?qū)?#xff1a;

// error : Point::freeList;

????? 我們必須顯式地指定類型,才能使用freeList:

// ok: Point<float>::freeList;

????? 像上面這樣使用static member,會(huì)使其一份實(shí)例與Point class的float instantiation在程序中

產(chǎn)生關(guān)聯(lián)。如果我們寫:

// ok : 另一個(gè)實(shí)例(instance) Point<double>::freeList;

???? 就會(huì)出現(xiàn)第二個(gè)freeList實(shí)例,與Point class的double instantiation產(chǎn)生關(guān)聯(lián)。

???? 如果我們定義一個(gè)指針,指向特定的實(shí)例,像這樣:

Point<float> *ptr = 0;

???? 再一次,程序中什么也沒發(fā)生。因?yàn)橐粋€(gè)指向class object的指針,本身并不是一個(gè)class

object,編譯器不需要知道與該class有關(guān)的任何members的數(shù)據(jù)或object布局?jǐn)?shù)據(jù)。所以將

“Point的一個(gè)float實(shí)例”實(shí)例化也就沒有必要了。在C++ Standard完成之前,“聲明一個(gè)指針指向

某個(gè)template class”這件事情并未被強(qiáng)制定義,編譯器可以自行決定要或不要將template“實(shí)例

化”。cfront就是這么做的!如今C++ Standard已經(jīng)禁止編譯器這么做。

????? 如果不是pointer而是reference,又如何?假設(shè):

const Point<float> &ref = 0;

????? 它真的會(huì)實(shí)例化一個(gè)“Point的float實(shí)例”。這個(gè)定義的真正語意會(huì)被擴(kuò)展為:

// 內(nèi)部擴(kuò)展 Point<float> temporary( float ( 0 ) ); const Point<float> &ref = temporary;

????? 因?yàn)閞eference并不是無物(no object)的代名詞。0被視為整數(shù),必須被轉(zhuǎn)換為以下類型的

一個(gè)對(duì)象:

Point <float>

?????? 如果沒有轉(zhuǎn)換的可能,這個(gè)定義就是錯(cuò)誤的,會(huì)在編譯時(shí)被挑出來。

?????? 所以,一個(gè)class object的定義,不論是由編譯器暗中地做(像稍早程序代碼中出現(xiàn)過的

temporary),或是由程序員像下面這樣顯示地做:

const Point <float> origin;

?????? 都會(huì)導(dǎo)致template class的“實(shí)例化”,也就是說,float instantiation的真正對(duì)象布局會(huì)被產(chǎn)生

出來?;仡櫹惹暗膖emplate聲明,我們看到Point有三個(gè)nonstatic members,每一個(gè)的類型都是

Type。Type現(xiàn)在被綁定為float,所以origin的配置空間必須足夠容納三個(gè)float成員。

?????? 然而,member functions(至少對(duì)于那些未被使用過的)不應(yīng)該被“實(shí)例化”。只有在

member functions被使用的時(shí)候,C++ Standard才要求它們被“實(shí)例化”。目前的編譯器并不精確

遵循這項(xiàng)要求。之所以由使用者來主導(dǎo)“實(shí)例化”(instantiation)規(guī)則,有兩個(gè)主要原因:

????? 1)空間和時(shí)間效率的考慮。如果class中有100個(gè)member functions,但程序里只針對(duì)某個(gè)

類型使用其中兩個(gè),針對(duì)另一個(gè)類型使用其中五個(gè),那么將其他193個(gè)函數(shù)都“實(shí)例化”將花費(fèi)大

量的時(shí)間和空間。

????? 2)尚未實(shí)現(xiàn)的機(jī)能。并不是一個(gè)template實(shí)例化的所有類型就一定能夠完整支持一組

member functions所需要的所有運(yùn)算符。如果只“實(shí)例化”那些真正用到的member

functions,template就能夠支持那些原本可能會(huì)造成編譯時(shí)期錯(cuò)誤的類型(types)。

? ? ? 舉個(gè)例子,origin的定義需要調(diào)用Point的default constructor和destructor,那么只有這兩個(gè)

函數(shù)需要被“實(shí)例化”。類似的道理,當(dāng)寫下:

Point <float> *p = new Point <float>;

? ? ?? 時(shí),只有(1)Point template的float實(shí)例、(2)new運(yùn)算符、(3)default constructor需要

被“實(shí)例化”。有趣的是,雖然new運(yùn)算符是這個(gè)class的一個(gè)implicitly static member,以至于它

不能夠處理其中任何一個(gè)nonstatic members,但它還是依賴真正的template參數(shù)類型,因?yàn)樗?/p>

的第一參數(shù)size_t代表class的大小。 ??

?????? 這些函數(shù)在什么時(shí)候“實(shí)例化”?目前流行兩種策略:

?????? 1)在編譯的時(shí)候,那么函數(shù)將“實(shí)例化”于origin和p存在的那個(gè)文件中。

?????? 2)在鏈接的時(shí)候。那么編譯器會(huì)被一些輔助工具重新激活。template函數(shù)實(shí)例可能被放在

這一文件中、別的文件中或一個(gè)分離的存儲(chǔ)位置。

?????? 在“int和long一致”(或“double和long double一致”)的架構(gòu)之中,兩個(gè)類型實(shí)例化操作:

Point <int> pi; Point <long> pl;

?????? 應(yīng)該產(chǎn)生一個(gè)還是兩個(gè)實(shí)例呢?目前所知道的所有編譯器都產(chǎn)生兩個(gè)實(shí)例(可能有兩組

完整的member functions)。C++ Standard并未對(duì)此有什么強(qiáng)制規(guī)定。

#include <iostream>template <class Type> class Point {public:enum Status { unallocated, normalized };Point( Type x = 0.0, Type y = 0.0, Type z = 0.0 ): _x( x ), _y( y ), _z( z ) { }~Point() { }void* operator new( size_t size ) { return ::operator new( size ); }void operator delete( void* pointee ) { ::operator delete( pointee ); } Type y() { return _y; }// ...public:static Point<Type> *freeList;static int chunkSize; Type _x, _y, _z; };int main() {Point<float> *ptr = 0;const Point<float> &ref = 0;Point<float> *p = new Point<float>;Point<int> pi;Point<long> pl;std::cout << "sizeof( *ptr ) = " << sizeof( *ptr ) << std::endl;//std::cout << "ptr->_x = " << ptr->_x << std::endl; std::cout << "sizeof( ref ) = " << sizeof( ref ) << std::endl;std::cout << "ref._x = " << ref._x << std::endl;std::cout << " &pi = " << &pi << std::endl;std::cout << " &pl = " << &pl << std::endl; }

? ? 當(dāng)輸出“ptr->_x”時(shí):

?

???? 可以看到,指針ptr的確未產(chǎn)生對(duì)象。

???? 當(dāng)輸出“ref._x”時(shí):

????? 可以看到引用產(chǎn)生了對(duì)象實(shí)例。pi和pl是不同的實(shí)例。

在匯編生成的代碼中有:

_ZN5PointIfEC2Efff // Point<float>::Point(float, float, float) _ZN5PointIfED2Ev // int<float>::~Point() _ZN5PointIfEnwEj // Point<float>::operator new(unsigned int) _ZN5PointIiEC2Eiii // Point<int>::Point(int, int, int) _ZN5PointIiED2Ev // Point<int>::~Point() _ZN5PointIlEC2Elll // Point<long>::Point(long, long, long) _ZN5PointIlED2Ev // Point<long>::~Point()

???? ?? 可以看到的確沒有產(chǎn)生y()函數(shù)和operator delete( void* pointee )函數(shù)的代碼,也產(chǎn)生了int

和long的兩組完整的member functions。

?? 2、Template的錯(cuò)誤報(bào)告(Error Reporting within a Template)

????? 考慮下面的template聲明:

(1) template <class T> (2) class Mumble (3) { (4) public$: (5) Mumble( T t = 1024 ) (6) : _t( t ) (7) { (8) if( tt != t ) (9) throw ex ex; (10) } (11) private: (12) T tt; (13) }

?????? 這個(gè)Mumble template class的聲明內(nèi)含一些既露骨又潛沉的錯(cuò)誤:

????? 1)L4:使用$4字符是不對(duì)的。這項(xiàng)錯(cuò)誤有兩方面。第一,$并不是一個(gè)可以合法用于標(biāo)識(shí)

符的字符;第二,class聲明中只允許有public、protected、private三個(gè)標(biāo)簽(labels),$的出現(xiàn)

使public$不成為public。第一點(diǎn)是語匯(lexical)上的錯(cuò)誤,第二點(diǎn)則是造句/解析(syntactic

/parsing)上的錯(cuò)誤。

?????? 2)L5:t被初始化為整數(shù)常量1024,或許可以,也或許不可以,視T的真實(shí)類型而定。一般

而言,只有template的各個(gè)實(shí)例才診斷得出來。

?????? 3)L6:_t并不是哪一個(gè)member的名稱,tt才是。這種錯(cuò)誤一般會(huì)在“類型檢驗(yàn)”這個(gè)階段被

找出來。是的,每一個(gè)名稱必須綁定于一個(gè)定義身上,要不就會(huì)產(chǎn)生錯(cuò)誤。

?????? 4)L8:!=運(yùn)算符可能已定義好,但也可能還沒有,視T的真正類型而定。和第二點(diǎn)一

樣,只有template的各個(gè)實(shí)例才診斷得出來。

???? ? 5)L9:我們意外地鍵入ex兩次。這個(gè)錯(cuò)誤會(huì)在編譯時(shí)期的解析(parsing)階段被發(fā)現(xiàn)。

C++語言中一個(gè)合法的句子不允許一個(gè)標(biāo)識(shí)符緊跟在另一個(gè)標(biāo)識(shí)符之后。

?????? 6)L13:我們忘記了一個(gè)分號(hào)作為class聲明的結(jié)束。這項(xiàng)錯(cuò)誤也會(huì)在編譯時(shí)期的語句分析

(parsing)階段被發(fā)現(xiàn)。

????? 在一個(gè)nontemplate class聲明中,這6個(gè)既露骨又潛沉的錯(cuò)誤會(huì)被編譯器挑出來。但

template class卻不同。例如,所有與類型有關(guān)的檢驗(yàn),如果牽涉到template參數(shù),都必須延遲

到真正實(shí)例化操作(instantiation)發(fā)生,才得為之。也就是說,L5和L8的潛在錯(cuò)誤會(huì)在每個(gè)實(shí)

例操作(instantiation)發(fā)生時(shí)被檢查出來并記錄之,其結(jié)果將因不同的實(shí)際類型而不同。于是

結(jié)果:

#include <iostream>template <class T> class Mumble {public$:Mumble( T t = 1024 ): _t( t ){if( tt != t )throw ex ex;}private:T tt; };int main() {std::cout << "Hello World!" << std::endl; }

????

????? 當(dāng)只修改"public$"這一行時(shí):

public:

??

??????? 修改"_t(t)"和"ex"兩行:

int ex;...: tt( t )...throw ex;

??????? 通過編譯:

??????? 當(dāng)在main函數(shù)里添加:

Mumble<int> mi;

則L5和L8是正確的,編譯通過:

?????? 而如果:

Mumble<int*> pmi;

??????

?????? 那么L8正確L5錯(cuò)誤,因?yàn)椴荒軌驅(qū)⒁粋€(gè)整數(shù)常量(除了0)指定給一個(gè)指針。

?????? 面對(duì)這樣的聲明:

class SmallInt {public:SmallInt( int _x ) : x( _x ) { }// ...private:int x; };

??????? 由于其!=運(yùn)算并未定義,所以下面的句子:

Mumble<SmallInt> smi;

??

??????? 會(huì)造成L8錯(cuò)誤,而L5正確。當(dāng)然,下面這個(gè)例子:

Mumble<SmallInt*> psmi;

?????? 又造成L8正確而L5錯(cuò)誤。

?????? 那么,什么樣的錯(cuò)誤會(huì)在編譯器處理template聲明時(shí)被標(biāo)示出來?這有一部分和template的

處理策略有關(guān)。cfront對(duì)template的處理完全解析(parse)但不做類型檢驗(yàn);只有在每一個(gè)實(shí)例

化操作(instantiation)發(fā)生時(shí)才做類型檢驗(yàn)。所以在一個(gè)parsing策略之下,所有語匯

(lexing)錯(cuò)誤和解析(parsing)錯(cuò)誤都會(huì)在處理template聲明的過程中被標(biāo)示出來。

??????? 語匯分析器(lexical analyzer)會(huì)在L4捕捉到一個(gè)不合法的字符,解析器(parser)會(huì)這

樣標(biāo)示它:

public$: // caught

??????? 表示這是一個(gè)不合法的標(biāo)簽(label)。解析器(parser)不會(huì)把“對(duì)一個(gè)未命名的member

作出參考操作”視為錯(cuò)誤:

_t( t ) // not caught

??????? 但它會(huì)抓出L9“ex出現(xiàn)兩次”以及L13“缺少一個(gè)分號(hào)”這兩種錯(cuò)誤。

??????? 在一個(gè)十分普遍的代替策略中,template的聲明被收集成一系列的“l(fā)exical tokens”,而

parsing操作延遲直到真正有實(shí)例化操作(instantiation)發(fā)生時(shí)才開始。每當(dāng)看到一個(gè)

instantiation發(fā)生,這組token就被推往parser,然后調(diào)用類型檢驗(yàn),等等。面對(duì)先前出現(xiàn)的那個(gè)

template聲明,“l(fā)exical tokenizing”會(huì)指出什么錯(cuò)誤嗎?事實(shí)上很少,只有L4所使用的不合法字

符會(huì)被指出。其余的template聲明都被解析為合法的tokens并被收集起來。

??????? 目前的編譯器,面對(duì)一個(gè)template聲明,在它被一組實(shí)際參數(shù)實(shí)例化之前,只能施行以有

限的錯(cuò)誤檢查。template中那些與語法無關(guān)的錯(cuò)誤,程序員可能認(rèn)為十分明顯,編譯器卻通過

了,只有在特定實(shí)例被定義之后,才會(huì)發(fā)出抱怨。這是目前實(shí)現(xiàn)技術(shù)上的一個(gè)大問題。

??????? Nonmember和member template functions在實(shí)例化行為(instantiation)發(fā)生之前也一樣

沒有做完完全的類型檢驗(yàn)。這導(dǎo)致某些十分露骨的template錯(cuò)誤聲明竟然得以通過編譯。例如

下面的template聲明:

template<class type> class Foo {public:Foo();type val();void val( type v );private:type _val; }; // bogus_member不是class的一個(gè)member function // dbx不是class的一個(gè)data member template <class type> double Foo<type>::bogus_member() { return this->dbx; }

?????? 在g++4.8.4中,編譯結(jié)果如下:

?????? 可以看到class中的函數(shù)被顯示出錯(cuò)誤。

??????? 如果在class中加入成員函數(shù):

template<class type> class Foo {public:Foo();type val();void val( type v );double bogus_member();private:type _val; };

????????? 編譯通過,并不會(huì)報(bào)沒有dbx的錯(cuò)誤。

????????? 這些都是編譯器設(shè)計(jì)者自己的決定。Template facility并沒有說不允許對(duì)template聲明的類

型部分有更嚴(yán)格的檢驗(yàn)。

?????? 3、Template 中的名稱決議法(Name Resolution within a Template)

?????? 必須能夠區(qū)分以下兩種意義。一種是C++ Standard所謂的“scope of the template

definition”,也就是“定義出template”的程序端。另一種是C++ Standard所謂的“scope of the

template instantiation”,也就是“實(shí)例化template”的程序端。第一種情況舉例如下:

// scope of the template definition extern double foo( double );template<class type> class ScopeRules {public:void invariant(){_member = foo( _val );}type type_dependent(){return foo( _member );}// ...private:int _val;type _member; };

?????? 第二種情況舉例如下:

// scope of the template instantiation extern int foo( int ); // ... ScopeRules<int> sr0;

?????? 在ScopeRules template中有兩個(gè)foo()調(diào)用操作。在“scope of template definition”中,只有

一個(gè)foo()函數(shù)聲明位于scope之內(nèi)。然而在“scope of template instantiation”中,兩個(gè)foo()函數(shù)聲

明都位于scope之內(nèi)。如果我們有一個(gè)函數(shù)調(diào)用操作:

// scope of the template instantiation sr0.invariant();

??????? 那么,在invariant()中調(diào)用的究竟是哪一個(gè)foo()函數(shù)實(shí)例呢?

// 調(diào)用的是哪一個(gè)foo()函數(shù)實(shí)例? _member = foo( _val );

??????? 在調(diào)用操作的那一點(diǎn)上,程序中的兩個(gè)函數(shù)實(shí)例是:

// scope of the template declaration extern double foo( double );// scope of the template instantiation extern int foo( int );

??????? 而_val的類型是int。結(jié)果被選中的是直覺以外的那一個(gè):

// scope of the template declaration extern double foo ( double );

??????

?????? Template之中,對(duì)于一個(gè)nonmember name的決議結(jié)果,是根據(jù)這個(gè)name的使用是否與“用

以實(shí)例化該template的參數(shù)類型”有關(guān)而決定的。如果其使用互不相關(guān),那么就以“scope of the

template declaration”來決定name。如果其使用互有關(guān)聯(lián),那么就以“scope of the template

instantiation”來決定name。在第一個(gè)例子中,foo()與用以實(shí)例化ScopeRules的參數(shù)類型無關(guān):

// the resolution of foo() is not // dependent on the template argument _member = foo( _val );

??????? 這是因?yàn)開val的類型是int:_val是一個(gè)“類型不會(huì)變動(dòng)”的template class member。也就是

說,被用來實(shí)例化這個(gè)template的真正類型,對(duì)于_val的類型并沒有影響。此外,函數(shù)的決議結(jié)

果只和函數(shù)的原型(signature)有關(guān),和函數(shù)的返回值沒有關(guān)系。因此_member的類型并不會(huì)

影響哪一個(gè)foo()實(shí)例被選中。foo()的調(diào)用與template參數(shù)毫無關(guān)系!所以調(diào)用操作必須根據(jù)

“scope of the template declaration”來決議。在此scope中,只有一個(gè)foo()候選者(注意,這種

行為不能夠以一個(gè)簡(jiǎn)單的宏擴(kuò)展——像是使用一個(gè)#define宏——重現(xiàn)之)。

?????? 讓我們另外看看“與類型相關(guān)”(type-dependent)的用法:

sr0.type_dependent();

?????? 這個(gè)函數(shù)的內(nèi)容如下:

return foo( _member );

?????? 這個(gè)例子很清楚地與template參數(shù)有關(guān),因?yàn)樵搮?shù)將決定_member的真正類型。所以這一

次foo()必須在“scope of the template instantiation”中決議,本例中這個(gè)scope有兩個(gè)foo()函數(shù)聲

明。由于_member的類型在本例中為int,所以應(yīng)該是int版的foo()。如果ScopeRules以double

類型實(shí)例化,那么就應(yīng)該是double版的foo()出現(xiàn)。如果ScopeRules是以double類型實(shí)例化,那

么該調(diào)用操作就曖昧不明。最后,如果ScopeRules以某一個(gè)class類型實(shí)例化,而該class沒有

針對(duì)int或double實(shí)現(xiàn)出convertion運(yùn)算符,那么foo()調(diào)用操作會(huì)被表示為錯(cuò)誤。不管如何演變,

都是由“scope of the template instantiation”來決定,而不是由“scope of the template

declaration”。

?????? 這意味著一個(gè)編譯器bib保持兩個(gè)scope contexts:

????? 1)“scope of the template declarartion”,用以專注于一般的template class。

????? 2)“scope of the template instantiation”,用以專注于特定的實(shí)例。

????? 編譯器的決議(resolution)算法必須決定哪一個(gè)才是適當(dāng)?shù)膕cope,然后在其中搜索適當(dāng)?shù)?/p>

name。

?????? 4、Member Function的實(shí)例化行為(Member Funciton Instantiation)

?????? 對(duì)于template的支持,最困難的莫過于template function的實(shí)例化(instantiation)。目前編

譯器提供了兩個(gè)策略:一個(gè)是編譯時(shí)期策略,程序代碼在program text file中備妥可用:另一個(gè)

是鏈接時(shí)期策略,有一些meta-compilation工具可以導(dǎo)引編譯器的實(shí)例化行為

(instantiation)。

??????? 下面是編譯器設(shè)計(jì)者必須回答的三個(gè)主要問題:

??????? 1)編譯器如何找出函數(shù)的定義?

???????? 答案之一是包含template program text file,就好像它是一個(gè)header文件一樣。Borland編

譯器就遵循這個(gè)策略。另一種方法是要求一個(gè)文件命名規(guī)則,例如,我們可以要求,在Point.h

文件中發(fā)現(xiàn)的函數(shù)聲明,其template program text一定要放置于文件Point.C或Point.cpp中,依

此類推。cfront就遵循這個(gè)策略。Edison Design Group編譯器對(duì)這兩種策略都支持。

???????? 2)編譯器如何能夠只實(shí)例化程序中用到的member functions?

???????? 解決辦法之一就是,根本忽略這項(xiàng)要求,把一個(gè)已經(jīng)實(shí)例化的class的所有member

functions都產(chǎn)生出來。Borland就是這么做的——雖然它也提供#pragmas可以壓制(或?qū)嵗?/p>

化)特定實(shí)例。另一種策略就是模擬鏈接操作,檢測(cè)看看哪一個(gè)函數(shù)真正需要,然后只為它

(們)產(chǎn)生實(shí)例。cfront就是這么做的。Edison Design Group編譯器對(duì)這兩種策略都支持。

???????? 3)編譯器如何阻止member definition在多個(gè).o文件中都被實(shí)例化呢?

????????? 解決辦法之一就是產(chǎn)生多個(gè)實(shí)例,然后從鏈接器中提供支持,只留下其中一個(gè)實(shí)例,其余

都忽略。另一個(gè)辦法就是由使用者來引導(dǎo)“模擬鏈接階段”的實(shí)例化策略,決定哪些實(shí)例

(instance)才是所需求的。

????????? 目前,不論是編譯時(shí)期還是鏈接時(shí)期的實(shí)例化(instantiation)策略,均存在以下弱點(diǎn):

當(dāng)template實(shí)例被產(chǎn)生出來時(shí),有時(shí)候會(huì)大量增加編譯時(shí)間。很明顯,這將是template

functions第一次實(shí)例化時(shí)的必要條件。然而當(dāng)那些函數(shù)被非必要地再次實(shí)例化,或是當(dāng)“決定那

些函數(shù)是否需要再實(shí)例化”所花的代價(jià)太大時(shí),編譯器的表現(xiàn)令人失望!

??????? C++支持template的原始意圖可以想見是一個(gè)由使用者導(dǎo)引的自動(dòng)實(shí)例化機(jī)制(use-

directed automatic instantiation mechanism),既不需要使用者的介入,也不需要相同文件有

多次的實(shí)例化行為。但是這已被證明是非常難以達(dá)成的任務(wù),比任何人此刻所能想象的還要

難。ptlink,隨著cfront3.0版所附的原始實(shí)例化工具,提供了一個(gè)由使用者驅(qū)動(dòng)的自動(dòng)實(shí)例化機(jī)

制(use-driven automatic instantiation mechanism),但是它實(shí)在太復(fù)雜了,即使是久經(jīng)世故

的人也沒辦法一下子了解。

???????? Edison Design Group開發(fā)出一套第二代的directed-instantiation機(jī)制,非常接近于

template facility原始含義。它主要運(yùn)作如下:

??????? 1)一個(gè)程序的原始碼被編譯時(shí),最初并不會(huì)產(chǎn)生任何“template實(shí)例化”。然而,相關(guān)信息

已經(jīng)被產(chǎn)生于object files之中。

??????? 2)當(dāng)object files被鏈接在一塊時(shí),會(huì)有一個(gè)prelinker程序被執(zhí)行起來。它會(huì)檢查object

files,尋找template實(shí)例的相互參考以及對(duì)應(yīng)的定義。

??????? 3)對(duì)于每一個(gè)“參考到template實(shí)例”而“該實(shí)例卻沒有定義”的情況,prelinker將該文件視為

與另一個(gè)實(shí)例化(在其中,實(shí)例已經(jīng)實(shí)例化)等同。以這種方法,就可以將必要的程序?qū)嵗?/p>

操作指定給特定的文件。這些都會(huì)注冊(cè)在prelinker所產(chǎn)生的.ii文件中(放在磁盤目錄ii_file)。

??????? 4)prelinker重新執(zhí)行編譯器,重新編譯每一個(gè)“.ii文件曾被改變過”的文件。這個(gè)過程不斷

重復(fù),直到所有必要的實(shí)例化操作都已完成。

??????? 5)所有的object files被鏈接成一個(gè)可執(zhí)行文件。

??????? 這種direct-instantiation體制的主要成本在于,程序第一次被編譯時(shí)的.ii文件設(shè)定時(shí)間。次

要成本則是必須針對(duì)每一個(gè)“complie afterwards”執(zhí)行prelinker,以確保所有被參考到的

templates都存在著定義。在最初的設(shè)計(jì)以及成功地第一次鏈接之后,重新編譯操作包含以下程

序:

????? 1)對(duì)于每一個(gè)將被重新編譯的program text file,編譯器檢查其對(duì)應(yīng)的.ii文件。

????? 2)如果對(duì)應(yīng)的.ii文件列出一組要被實(shí)例化(instantiated)的templates,那些templates(而

且只有那些templates)會(huì)在此編譯時(shí)被實(shí)例化。

?????? 3)prelinker必須執(zhí)行起來,確保所有被參考到的template已經(jīng)被定義妥當(dāng)。

?????? 出現(xiàn)某種形式的automated template機(jī)制,是“對(duì)程序員友善的C++編譯系統(tǒng)”的一個(gè)必要組

件。

?????? 不幸的是,沒有任何一個(gè)機(jī)制是沒有bugs的。Edison Design Group的編譯器使用了一個(gè)由

cfront2.0引入的算法,針對(duì)程序中的每一個(gè)class自動(dòng)產(chǎn)生virtual table的單一實(shí)例。例如下面的

class聲明:

class PrimitiveObject : public Geometry {public:virtual ~PrimitiveObject();virtual void draw();... };

?????? 如果它被含入于15個(gè)或45個(gè)程序源碼中,編譯器如何能夠確保只有一個(gè)virtual table實(shí)例被

產(chǎn)生出來呢?產(chǎn)生15份或45份實(shí)例倒還容易些!

?????? Koenig以下面的方法解決這個(gè)問題:每一個(gè)virtual function的地址都被放置于active classes

的virtual table中。如果取得函數(shù)地址,表示virtual function的定義必定出現(xiàn)在程序的某個(gè)地點(diǎn);

否則程序就無法鏈接成功。此外,此函數(shù)只能有一個(gè)實(shí)例,否則也是鏈接不成功。那么,就把

virtual table放在定義了該class之第一個(gè)non-inline、nonpure virtual function的文件中。以我們

的例子而言,編譯器會(huì)將virtual table產(chǎn)生在存儲(chǔ)著virtual destructor的文件之中。

??????? 不幸的是,在template之中,這種單一定義并不一定為真。在template所支持的“將模塊中

的每一樣?xùn)|西都編譯”的模型下,不只是多個(gè)定義可能被產(chǎn)生,而且鏈接器也放任讓多個(gè)定義同

時(shí)出現(xiàn),它只要選擇其中一個(gè)而將其余都忽略,也就是了。

???????? 但Edison Design Group的automatic instantiation機(jī)制做什么事呢?考慮下面這個(gè)library函

數(shù):

void foo( const Point<float> *ptr ) {ptr->virtual_func(); }

?????? virtual function call被轉(zhuǎn)換為類似這樣的東西:

// C++偽碼 // ptr->virtual_func(); ( *ptr->_vtbl_Point<float>[ 2 ] )( ptr );

??????? 于是導(dǎo)致實(shí)例化(instantiated)Point class的一個(gè)float實(shí)例及其virtual_func()。由于每一個(gè)

virtual function的地址被放置于table之中,如果virtual table被產(chǎn)生出來,每一個(gè)virtual function

也都必須被實(shí)例化(instantiated)。這就是為什么C++ Standard有下面的文字說明的緣故:

?????? 如果一個(gè)vitual function被實(shí)例化(instantiated),其實(shí)例化點(diǎn)緊跟在其class的實(shí)例化點(diǎn)之

后。

?????? 然而,如果編譯器遵循cfront的virtual table實(shí)現(xiàn)體制,那么在”Point的float實(shí)例有一個(gè)virtual

destructor定義被實(shí)例化“之前,這個(gè)table不會(huì)被產(chǎn)生。除非,在這一點(diǎn)上,并沒有顯式使用

virtual destructor以擔(dān)保其實(shí)例化行為(instantiation)。

?????? Edison Design Group的automatic template機(jī)制并不明白它自己的編譯器對(duì)第一個(gè)non-

inline、nonpure virtual function的隱式使用,所以并沒有把它標(biāo)于.ii文件中。結(jié)果,鏈接器反而

回頭抱怨下面這個(gè)符號(hào)沒有出現(xiàn):

_vtbl_Point<float>

?????? 并拒絕產(chǎn)生一個(gè)可執(zhí)行文件。Automatic instantiation在此失效!程序員必須顯式地強(qiáng)迫將

destructor實(shí)例化。目前的編譯系統(tǒng)以#program指令來支持此需求。然而C++ Standard也已經(jīng)

擴(kuò)充了對(duì)template的支持,允許程序員顯式地要求在一個(gè)文件中將整個(gè)class template實(shí)例化:

template class Point3d<float>;

?????? 或是針對(duì)一個(gè)template class的個(gè)別member function:

template float Point3d<float>::X() const;

?????? 或是針對(duì)一個(gè)個(gè)別template function:

template Point3d<float> operator+ ( const Point3d<float>&, const Point3d<float>& );

??????? 實(shí)際上,template instantitation似乎拒絕了全面的自動(dòng)化。甚至雖然每一件工作都做對(duì)了,

產(chǎn)生出來的object files的重新編譯成本仍然可能很高——如果程序十分巨大的話!以手動(dòng)方式先

在個(gè)別的object module中完成預(yù)先實(shí)例化操作(pre-instantiation),雖然沉悶,卻是唯一有效

率的做法。

二、異常處理(Exception Handling)

?????? 欲支持exception handling,編譯器的主要工作就是找出catch子句,以處理被拋

出來的exception。這多少需要追蹤程序堆棧中的每一個(gè)函數(shù)的目前作用區(qū)域(包括

追蹤函數(shù)中l(wèi)ocal class objects當(dāng)時(shí)的情況)。同時(shí),編譯器必須提供某種查詢

exception objects的方法,以知道其實(shí)際類型(這直接導(dǎo)致某種形式的執(zhí)行期類型識(shí)

別,也就是RTTI)。最后,還需要某種機(jī)制用以管理被拋出的object,包括它的產(chǎn)

生、存儲(chǔ)、可能的析構(gòu)(如果有相關(guān)的destructor)、清理(clean up)以及一般存

取。也可能有一個(gè)以上的objects同時(shí)起作用。一般而言,exception handling機(jī)制需

要與編譯器所產(chǎn)生的數(shù)據(jù)結(jié)構(gòu)以及執(zhí)行期的一個(gè)exception library緊密合作。在程序

大小和執(zhí)行速度之間,編譯器必須有所抉擇:

????? 1)為了維護(hù)執(zhí)行速度,編譯器可以在編譯時(shí)期建立起用于支持的數(shù)據(jù)結(jié)構(gòu)。這

會(huì)使程序的大小發(fā)生膨脹,但編譯器可以幾乎忽略這些結(jié)構(gòu),直到exception被拋

出。

??????? 2)為了維護(hù)程序大小,編譯器可以在執(zhí)行期建立起用于支持的數(shù)據(jù)結(jié)構(gòu)。這會(huì)影響程序的

執(zhí)行速度,但意味著編譯器只有在必要的時(shí)候才建立那些數(shù)據(jù)結(jié)構(gòu)(并且可以拋棄之)。

??? 1、Exception Handling快速檢閱

??? C++的exception handling由三個(gè)主要的語匯組件構(gòu)成:

??? 1)一個(gè)throw子句。它在程序某處發(fā)出一個(gè)exception。被拋出去的exception可以是內(nèi)建類

型,也可以是使用者自定類型。

??? 2)一個(gè)或多個(gè)catch子句。每一個(gè)catch子句都是一個(gè)exception handler。它用來表示說,這

個(gè)子句準(zhǔn)備處理某種類型的exception,并且在封閉的大括號(hào)區(qū)段中提供實(shí)際的處理程序。

??? 3)一個(gè)try區(qū)段。它被圍繞以一系列的敘述句(statements),這些敘述句可能會(huì)引發(fā)catch

子句起作用。

????? 當(dāng)一個(gè)exception被拋出去時(shí),控制權(quán)會(huì)從函數(shù)調(diào)用中被釋放出來,并尋找一個(gè)吻合的catch

子句。如果沒有吻合者,那么默認(rèn)的處理例程terminate()會(huì)被調(diào)用。當(dāng)控制權(quán)被放棄后,堆棧中

的每一個(gè)函數(shù)調(diào)用也就被推離(popped up)。這個(gè)程序稱為unwinding the stack。在每一個(gè)函

數(shù)被推離堆棧之前,函數(shù)的local class objects的destructor會(huì)被調(diào)用。

????? Exception handling 中比較不那么直覺的就是它對(duì)于那些似乎沒什么事做的函數(shù)所帶來的沖

擊。例如下面這個(gè)函數(shù):

(1) Point* (2) mumble() (3) { (4) Point *ptl, *pt2; (5) pt1 = foo(); (6) if( !pt1 ) (7) return 0; (8) (9) Point p; (10) (11) pt2 = foo(); (12) if( !pt2 ) (13) return pt1; (14) (15) ... (16) }

?????? 如果有一個(gè)exception在第一次調(diào)用foo()(L5)時(shí)被拋出,那么這個(gè)mumble()函數(shù)會(huì)被推出程

序堆棧。由于調(diào)用foo()的操作并不在一個(gè)try區(qū)段之內(nèi),也就不需要嘗試和一個(gè)catch子句吻合。

這里也沒有任何local class objects需要析構(gòu)。然而如果有一個(gè)exception在第二次調(diào)用foo()

(L11)時(shí)被拋出,exception handling機(jī)制就必須在”從程序堆棧中”unwindling“這個(gè)函數(shù)“之

前,先調(diào)用p的destructor。

????? 在exception handling之下,L4~L8和L9~L16被視為兩塊語意不同的區(qū)域,因?yàn)楫?dāng)exception

被拋出來時(shí),這兩塊區(qū)域有不同的執(zhí)行期語意。而且,欲支持exception handling,需要額外的

一些”薄記“操作與數(shù)據(jù)。編譯器的做法有兩種:一種是把兩塊區(qū)域以個(gè)別的”將被摧毀之local

objects“鏈表(已在編譯時(shí)期設(shè)妥)聯(lián)合起來;另一種做法是讓兩塊區(qū)域共享同一個(gè)鏈表,該鏈表

會(huì)在執(zhí)行期擴(kuò)大或縮小。

????? 在程序員層面,exception handling也改變了函數(shù)在資源管理上的語意。例如,下面的函數(shù)

中含有對(duì)一塊共享內(nèi)存的locking和unlocking操作,雖然看起來和exceptions沒有什么關(guān)系,但

在exception handling之下并不保證能夠正確允許:

void mumble( void *arena ) {Point *p = new Point;smLock( arena ); // function call// 如果有一個(gè)exception在此發(fā)生,問題就來了// ...smUnLock( arena ); // function calldelete p; }

?????? 本例之中,exception handling機(jī)制把整個(gè)函數(shù)視為單一區(qū)域,不需要操心”將函數(shù)從程序堆

棧中“unwinding”的事情。然而從語意上來說,在函數(shù)被推出堆棧之前,我們需要unlock共享內(nèi)

存,并delete p。讓函數(shù)稱為“exception proof”的最明確(但不是最有效率)方法就是安插一個(gè)

default catch子句,像這樣:

void mumble( void *arena ) {Point *p p = new Point;try{smLock( arena ); // function call// ...}catch( ... ){smUnLock( arena );delete p;throw; }smUnLock( arena ); delete p; }

???? 這個(gè)函數(shù)現(xiàn)在有了兩個(gè)區(qū)域:

??? 1)try block以外的區(qū)域,在那里,exception handling機(jī)制除了“pop”程序堆棧之外,沒有其

他事情要做。

??? 2)try block以內(nèi)的區(qū)域(以及它所聯(lián)合的default catch子句)。

??? 請(qǐng)注意,new運(yùn)算符的調(diào)用并非在try區(qū)段內(nèi)。如果new運(yùn)算符或是Point constructor在配置內(nèi)

存之后發(fā)生一個(gè)exception,那么內(nèi)存既不會(huì)被unlocking,p也不會(huì)被delete(這兩個(gè)操作都在

catch區(qū)段內(nèi))。這是正確的語意嗎?

??? 是的,它是。如果new運(yùn)算符拋出一個(gè)exception,那么就不需要配置heap中的內(nèi)存,Point

constructor也不需要被調(diào)用。所以也就沒有理由調(diào)用delete運(yùn)算符。然而如果是在Point

constructor中發(fā)生exception,此時(shí)內(nèi)存已配置完成,那么Point之中任何構(gòu)建好的合成物或子對(duì)

象(subobject,也就是一個(gè)member class object或base class object)都將自動(dòng)被析構(gòu)掉,然

后heap內(nèi)存也會(huì)被釋放掉。不論哪種情況,都不需要delete運(yùn)算符。

???? 類似的道理,如果一個(gè)exception是在new運(yùn)算符執(zhí)行過程中被拋出的,arena所指向的內(nèi)存

就絕不會(huì)被locked,因此,也沒有必要unlock之。

???? 處理這些資源管理問題,一個(gè)建議辦法就是,將資源需求封裝于一個(gè)class object體內(nèi),并由

destructor來釋放資源(然而如果資源必須被索求、被釋放、再被索求、再被釋放......許多次的

時(shí)候,這種風(fēng)格會(huì)變得優(yōu)點(diǎn)累贅):

void mumble( void *arena ) {auto_ptr<Point> ph ( new Point );SMLock sm( arena );// 如果這里拋出一個(gè)exception,現(xiàn)在就沒有問題了// ...// 不需要顯式地unlock和delete// local destructors在這里被調(diào)用// sm.SMLock::~SMLock();// ph.auto_ptr<Point>::~auto_ptr<Point>() }

?????? 從exception handling的角度看,這個(gè)函數(shù)現(xiàn)在有三個(gè)區(qū)段:

????? 1)第一區(qū)是auto_ptr被定義之處。

????? 2)第二區(qū)段是SMLock被定義之處。

????? 3)上述兩個(gè)定義之后的整個(gè)函數(shù)。

?????? 如果exception是在auto_ptr constructor中被拋出的,那么就沒有active local objects需要被

EH機(jī)制摧毀。然而如果SMLock constructor中拋出一個(gè)exception,auto_ptr object必須在

“unwinding”之前先被摧毀。至于在第三個(gè)區(qū)段中,兩個(gè)local objects當(dāng)然都必須被摧毀。

??????? 支持EH,會(huì)使那些擁有member class subobjects或base class subobjects(并且它們也都

有constructors)的classes的constructor更復(fù)雜。一個(gè)class如果被部分構(gòu)造,其destructor必須

只施行于那些已被構(gòu)造的subobjets和(或)member objects身上。例如,假設(shè)class X有

member objects A, B和C,都各有一對(duì)constructor和destructor,如果A的constructor拋出一個(gè)

exception,不論A、B或C都不需要調(diào)用其destructor。如果B的constructor拋出一個(gè)

exception,A的destructor必須被調(diào)用,但C不用。處理所有這些意外事故,是編譯器的責(zé)任。

?????? 同樣的道理,如果程序員寫下:

// class Point3d : public Point2d { ... } Point3d *cvs = new Point3d[ 512 ];

? ? ?? 會(huì)發(fā)生兩件事:

? ? ?? 1)從heap中配置足以給512個(gè)Point3d objects所用的內(nèi)存。

? ? ?? 2)如果成功,先是Point2d constructor,然后是Point3d constructor,會(huì)施行于每一個(gè)元素

身上。 ?

?????? 如果#27元素的Point3d constructor拋出一個(gè)exception,會(huì)怎樣?對(duì)于#27元素,只有

Point2d destructor需要調(diào)用執(zhí)行。對(duì)于前26個(gè)元素,Point3d destructor和Point2d destructor都

需要調(diào)用執(zhí)行。然后內(nèi)存必須被釋放回去。

??? 2、對(duì)Exception Handling的支持

? ?? 當(dāng)一個(gè)exception發(fā)生時(shí),編譯系統(tǒng)必須完成以下事情:

? ? 1)檢驗(yàn)發(fā)生throw操作的函數(shù)。

? ? 2)決定throw操作是否發(fā)生在try區(qū)段中。

? ? 3)若是,編譯系統(tǒng)必須把exception type拿來和每一個(gè)catch子句進(jìn)行比較。

? ? 4)如果比較后吻合,流程控制應(yīng)該交到catch子句手中。

? ? 5)如果throw的發(fā)生并不在try區(qū)段中,或沒有一個(gè)catch子句吻合,那么系統(tǒng)必須(a)摧毀

所有active local objects,(b)從堆棧中將目前的函數(shù)“unwind”掉,(c)進(jìn)行到程序堆棧的下

一個(gè)函數(shù)中去,然后重復(fù)上述步驟2~5。?

???? 決定throw是否發(fā)生在一個(gè)try區(qū)段中

???? 一個(gè)函數(shù)可以被想象為好幾個(gè)區(qū)域:

??? 1)try區(qū)段以外的區(qū)域,而且沒有active local objects。

??? 2)try區(qū)段以外的區(qū)域,但有一個(gè)(或以上)的active local objects需要析構(gòu)。

??? 3)try區(qū)段以內(nèi)的區(qū)域。

???? 編譯器必須表示出以上各區(qū)域,并使它們對(duì)執(zhí)行期的exception handling系統(tǒng)有所作用。一個(gè)

很棒的策略就是構(gòu)造出program counter-range表格。

????? program counter(EIP寄存器)內(nèi)含下一個(gè)即將執(zhí)行的程序指令。為了在一個(gè)內(nèi)含try區(qū)段的

函數(shù)中表示出某個(gè)區(qū)域,可以把program counter的起始值和結(jié)束值(或是起始值和范圍)存儲(chǔ)

在一個(gè)表格中。

????? 當(dāng)throw操作發(fā)生時(shí),目前的program counter值被拿來與對(duì)應(yīng)的“范圍表格”進(jìn)行對(duì)比,比決

定目前作用中的區(qū)域是否在一個(gè)try區(qū)域中。如果是,就需要找出相關(guān)的catch子句。如果這個(gè)

exception無法被處理(或者它被再次拋出),目前的這個(gè)函數(shù)會(huì)從程序中被推出(popped),

而program counter會(huì)被設(shè)定為調(diào)用端地址,然后這樣的循環(huán)再重新開始。

?????? 將exception的類型和每一個(gè)catch子句的類型做比較

?????? 對(duì)于每一個(gè)被拋出來的exception,編譯器必須產(chǎn)生一個(gè)類型描述器,對(duì)exception的類型進(jìn)

行編碼。如果那是一個(gè)derived type,編碼內(nèi)容必須包括其所有base class的類型信息。只編進(jìn)

public base class的類型是不夠的,因?yàn)檫@個(gè)exception可能被一個(gè)member function捕捉,而在

一個(gè)member function的范圍(scope)之中,derived class和nonpublic base class之間可以轉(zhuǎn)

換。

??????? 類型描述器(type descriptor)是必要的,因?yàn)檎嬲膃xception是在執(zhí)行期被處理的,其

object必須有自己的類型信息。RTTI正是因?yàn)橹С諩H而獲得的副產(chǎn)品。

??????? 編譯器還必須為每一個(gè)catch子句產(chǎn)生一個(gè)類型描述器。執(zhí)行期的exception handler會(huì)將“被

拋出之object的類型描述器”和“每一個(gè)cause子句的類型描述器”進(jìn)行比較,直到找到吻合的一

個(gè),或是直到堆棧已經(jīng)被“unwound”而terminate()已被調(diào)用。

?????? 每一個(gè)函數(shù)會(huì)產(chǎn)生一個(gè)exception表格,它描述與函數(shù)相關(guān)的各區(qū)域,任何必要的善后處理

代碼(cleanup code,被local class object destructors調(diào)用)以及catch子句的位置(如果某個(gè)

區(qū)域是在try區(qū)段之中的話)。

?????? 當(dāng)一個(gè)實(shí)際對(duì)象在程序執(zhí)行時(shí)被拋出,會(huì)發(fā)生什么事?

?????? 當(dāng)一個(gè)exception被拋出時(shí),exception object會(huì)被產(chǎn)生出來并通常放置在相同形式的

exception數(shù)據(jù)堆棧中。從throw端傳給catch子句的,是exceotion object的地址、類型描述器

(或是一個(gè)函數(shù)指針,該函數(shù)會(huì)傳回與該exception type有關(guān)的類型描述器對(duì)象)以及可能會(huì)有

的exception object描述器(如果有人定義它的話)。

????? 考慮一個(gè)catch子句如下:

catch ( exPoint p ) {// do somethingthrow; }

?????? 以及一個(gè)exception object,類型為exVertex,派生自exPoint。這兩種類型都吻合,于是

catch子句會(huì)作用起來。那么p會(huì)發(fā)生什么事?

?????? 1)p將以exception object作為初值,就像一個(gè)函數(shù)參數(shù)一樣。這意味著如果定義有(或由

編譯器合成出)一個(gè)copy constructor和一個(gè)destructor的話,它們都會(huì)實(shí)施于local copy身上。

?????? 2)由于p是一個(gè)object而不是一個(gè)reference,當(dāng)其內(nèi)容被拷貝的時(shí)候,這個(gè)exception

object的non-exPoint部分會(huì)被切掉(sliced off)。此外,如果為了exception的繼承而提供

virtual function,那么p的vptr會(huì)被設(shè)為exPoint的virtual table;exception object的vptr不會(huì)拷貝。?

? ? ?? 當(dāng)這個(gè)exception被再拋出一次時(shí),會(huì)發(fā)生什么事情?p現(xiàn)在是繁殖出來的object?還是從

throw端產(chǎn)生的原始exception object?p是一個(gè)local object,在catch子句的末端將被摧毀。拋出

p需要產(chǎn)生另一個(gè)臨時(shí)對(duì)象,并意味著喪失了原來的exception的exVertex部分。原來的

exception object被再一次拋出:任何對(duì)p的修改都會(huì)被拋棄。 ????

??????? 像下面這樣的一個(gè)catch子句:

catch( exPoint &rp ) {// do somethingthrow; }

??????? 則是參考到真正的exception object。任何虛擬調(diào)用都會(huì)被決議(resolved)為instances

active for exVertex,也就是exception object的真正類型。任何對(duì)此object的改變都被繁殖到下

一個(gè)catch子句。

??????? 最后,這里提出一個(gè)有趣的謎題。如果我們有下面的throw操作:

exVertex errVer;// ... mumble() {// ...if( mumble_cond ){errVer.fileName( "mumble()" );throw errVer;}// ... }

????? 究竟是真正的exception errVer被繁殖,還是errVer的一個(gè)復(fù)制品被構(gòu)造于exception stack之

中并不被繁殖?答案是一個(gè)復(fù)制品被構(gòu)造出來,全局性的errVer并沒有被繁殖。這意味著在一個(gè)

catch子句中對(duì)于exception object的任何改變都是局部性的,不會(huì)影響errVer。只是在一個(gè)catch

子句評(píng)估完畢并且知道它不會(huì)再拋出excption之后,真正的exception object才會(huì)被摧毀。

三、執(zhí)行期類型識(shí)別(Runtime Type Identification,RTTI)

???? 在cfront中,用以表現(xiàn)出一個(gè)程序的所謂“內(nèi)部類型體系”,看起來像這樣:

// 程序?qū)哟谓Y(jié)構(gòu)的根類(root class) class node { ... };// root of the 'type' subtree:basic types, // 'derived' types: pointers, arrays, // functions, classes, enums ... class type : public node { ... };// two representtations for functions class fct : public type { ... }; class gen : public type { ... };

??? 其中g(shù)en是generic的簡(jiǎn)寫,用來表現(xiàn)一個(gè)overloaded function。

??? 于是只要你有一個(gè)變量,或是類型為type*的成員(并知道它代表一個(gè)函數(shù)),你就必須決定

其特定的derived type是否為fct或是gen。在2.0之前,除了destructor之外唯一不能夠被overload

的函數(shù)就是conversion運(yùn)算符,例如:

class String {public:operator char*();// ... };

???? 在2.0導(dǎo)入const member functions之前,conversion運(yùn)算符不能夠被overload,因?yàn)樗鼈儾?/p>

使用參數(shù)。直到引進(jìn)了const member functions,情況才有所變化?,F(xiàn)在,像下面這樣的聲明就

可能了:

class String {public:// ok with Release 2.0operator char*();operator char*() const;// ... };

????? 也就是說,在2.0版本之前,以一個(gè)explicit cast來存取derived object總是安全(而且比較快

速)的,像下面這樣:

typedef type *ptype; typedef fct *pfct;simlipy_conv_op( ptype pt ) {// ok : conversion operators can only be fctspfct pf = pfct( pt );// ... }

????? 在const member functions引入之前,這份代碼是正確的。但之后就不對(duì)了。是因?yàn)镾tring

class聲明的改變,因?yàn)閏har* conversion運(yùn)算符現(xiàn)在被內(nèi)部視為一個(gè)gen而不是一個(gè)fct。

????? 下面這樣的轉(zhuǎn)換形式:

pfct pf = pfct( pt );

????? 被稱為downcast(向下轉(zhuǎn)換),因?yàn)樗行У匕岩粋€(gè)base class轉(zhuǎn)換至繼承架構(gòu)的末端,變

成其derived classes中某一個(gè)Downcast有潛在性的危險(xiǎn),因?yàn)樗糁屏祟愋拖到y(tǒng)的作用,不正

確的使用可能會(huì)帶來錯(cuò)誤的解釋(如果它是一個(gè)read操作)或腐蝕掉程序內(nèi)存(如果它是一個(gè)

write操作)。在我們的例子中,一個(gè)指向gen object的指針被不正確地轉(zhuǎn)換為一個(gè)指向fct object

的指針pf。所有后續(xù)對(duì)pf的使用都是不正確的(除非只是檢查它是否為0,或只是把它拿來和其

他指針進(jìn)行比較)。

???? 1、Type-Safe Downcast(保證安全的向下轉(zhuǎn)換操作)

???? C++被吹毛求疵的一點(diǎn)就是,它缺乏一個(gè)保證安全的downcast(向下轉(zhuǎn)換操作)。只有在“類

型真的可以被適當(dāng)轉(zhuǎn)換”的情況下,才能夠執(zhí)行downcast。一個(gè)type-safe downcast必須在執(zhí)行

期有所查詢,看看它是否指向它所展現(xiàn)(表達(dá))之object的真正類型。因此,欲支持type-safe

downcast,在object空間和執(zhí)行時(shí)間上都需要一些額外負(fù)擔(dān):

??? 1)需要額外的空間以存儲(chǔ)類型信息(type information),通常是一個(gè)指針,指向某個(gè)類型信

息節(jié)點(diǎn)。

??? 2)需要額外的空間以決定執(zhí)行期的類型(runtime type),因?yàn)?#xff0c;正如其名所示,這需要在

執(zhí)行期才能決定。

??? 這樣的機(jī)制面對(duì)下面這樣平常的C結(jié)構(gòu),會(huì)如何影響其大小、效率以及鏈接兼容性呢?

char *winnie_tbl[] = { "rumbly in my tummy", "oh, bother" };

??? 它所導(dǎo)致的空間和效率上的不良后果甚為可觀。

???? 沖突發(fā)生在兩組使用者之間:

??? 1)程序員大量使用多態(tài)(polymorphism),并因而需要正統(tǒng)而合法的大量downcast操作。

??? 2)程序員使用內(nèi)建數(shù)據(jù)類型以及非多態(tài)設(shè)備,因而不受各種額外負(fù)擔(dān)所帶來的不良后果。

???? 理想的解決方案是,為兩派使用者提供正統(tǒng)而合法的需要——雖然或許得犧牲一些設(shè)計(jì)上的

純度與優(yōu)雅性。

???? C++的RTTI機(jī)制提供了一個(gè)安全的downcast設(shè)備,但支隊(duì)那些展現(xiàn)”多態(tài)(也就是使用繼承

和動(dòng)態(tài)綁定)“的類型有效。我們?nèi)绾畏直孢@些?編譯器能否光看class 的定義就決定這個(gè)class

用以表現(xiàn)一個(gè)獨(dú)立的ADT還是一個(gè)支持多態(tài)的可繼承子類型(subtype)?當(dāng)然,策略之一就是

導(dǎo)入一個(gè)新的關(guān)鍵詞,優(yōu)點(diǎn)是可以清楚地識(shí)別支持新特性的類型,缺點(diǎn)則是必須翻新舊的程

序。

????? 另一個(gè)策略是通過聲明一個(gè)或多個(gè)virtual functions來區(qū)別class聲明。其優(yōu)點(diǎn)是透明化地將舊

有程序轉(zhuǎn)換過來,只要重新編譯就好。缺點(diǎn)則是可能會(huì)將一個(gè)起始并非必要的virtual function強(qiáng)

迫導(dǎo)入繼承體系的base class身上。這正是目前RTTI機(jī)制所支持的策略。在C++中,一個(gè)具備

多態(tài)性質(zhì)的class(所謂的pilymorphic class),正式內(nèi)含著繼承而來(或直接聲明)的virtual

functions。

????? 從編譯器的角度來說,這個(gè)策略還有其他優(yōu)點(diǎn),就是大量降低額外負(fù)擔(dān)。所有polymorphic

classes的objects都維護(hù)了一個(gè)指針(vptr),指向virtual function table。只要我們把與該class

相關(guān)的RTTI object地址放進(jìn)virtual table(通常是第一個(gè)slot),那么額外負(fù)擔(dān)就降低為:每一

個(gè)class object只多花費(fèi)一個(gè)指針。這一指針只需要被設(shè)定一次,它是被編譯器靜態(tài)設(shè)定的,而

非在執(zhí)行期由class constructor設(shè)定(vptr才是這么設(shè)定的)。

???? 2、Type-Safe Dynamic Cast(保證安全的動(dòng)態(tài)轉(zhuǎn)換)

????? dunamic_cast運(yùn)算符可以在執(zhí)行期決定真正的類型。如果downcast是安全的(也就是說,

如果base type pointer指向一個(gè)derived class object),這個(gè)運(yùn)算符會(huì)傳回適當(dāng)轉(zhuǎn)換過的指針。

如果downcast不是安全的,這個(gè)運(yùn)算符會(huì)傳回0。下面就我們?nèi)绾沃貙懳覀冊(cè)镜腸front

downcast:

typedef type *ptype; typedef fct *pfct;simplify_conv_op( ptype pt ) {if( pgct pf = dynamic_cast<pfct>( pt ) ){// ...process of}else { ... } }

???? 什么是dynamic_cast的真正成本呢?pfct的一個(gè)類型描述器會(huì)被編譯器產(chǎn)生出來。由pt所指向

的class object類型描述器必須在執(zhí)行期通過vptr取得。下面就是可能的轉(zhuǎn)換:

// 取得pt的類型描述器 ( ( type_info* )( pt->vptr[ 0 ] ) )->_type_descriptor;

????? type_info是C++ Standard所定義的類型描述器的class名稱,該class中放置著帶索求的類型

信息。virtual table的第一個(gè)slot內(nèi)含type_info object的地址;此type_info object與pt所指的class

type有關(guān)。這兩個(gè)類型描述器被交給一個(gè)runtime library函數(shù),比較之后告訴我們是否吻合。很

顯然這筆static cast昂貴得多,但卻安全得多(如果我們把一個(gè)fct類型”downcast”為一個(gè)gen類

型的話)。

???? 最初對(duì)runtime cast的支持提議中,并未引進(jìn)任何關(guān)鍵詞或額外的語法。下面這樣的轉(zhuǎn)換操

作:

// 最初對(duì)runtime cast的提議語法 pfct pf = pfct( pt );

???? 究竟是static還是dynamic,必須視pt是否指向一個(gè)多態(tài)class object而定。

???? 3、References并不是Pointers

???? 程序執(zhí)行中對(duì)一個(gè)class指針類型施以dynamic_cast運(yùn)算符,會(huì)獲得true或false:

?? 1)如果傳回真正的地址,則表示這一object的動(dòng)態(tài)類型被確認(rèn)了,一些與類型有關(guān)的操作現(xiàn)

在可以施行于其上。

?? 2)如果傳回0,則表示沒有指向任何object,意味著應(yīng)該以另一種邏輯施行于這個(gè)動(dòng)態(tài)類型未

確定的object身上。 ?

??? dynamic_cast運(yùn)算符也使用于reference身上。然而對(duì)于一個(gè)non-type-safe cast,其結(jié)果不會(huì)

與施行于指針的情況相同。為什么?一個(gè)reference設(shè)為0,會(huì)引起一個(gè)臨時(shí)性對(duì)象(擁有被參考

的類型)被產(chǎn)生出來,該臨時(shí)對(duì)象的初值為0,這個(gè)reference然后被設(shè)定成為該臨時(shí)對(duì)象的一個(gè)

別名(alias)。因此當(dāng)dynamic_cast運(yùn)算符施行于一個(gè)reference時(shí),不能夠提供對(duì)等于指針情

況下的那一組true/false。取而代之的是,會(huì)發(fā)生下列事情:

??? 1)如果reference真正參考到適當(dāng)?shù)膁erived class(包括下一層或下下一層,或下下下一層

或...),downcast會(huì)被執(zhí)行而程序可以繼進(jìn)行。

???? 2)如果reference并不真正是某一種derived class,那么,由于不能夠傳回0,因此拋出一個(gè)

bad_cast exception。

???? 下面是重新實(shí)現(xiàn)后的simplify_conv_op函數(shù),參數(shù)改為一個(gè)reference:

simplify_conv_op( const type &rt ) {try{fct &rf = dynamic_cast<fct&>( rt );// ... }catch( bad_cast ){// ... mumble ...} }

???? 其中執(zhí)行的操作十分理想地表現(xiàn)出某種exception failure,而不知是簡(jiǎn)單(一如從前)的控制

流程。

???? 4、Typeid運(yùn)算符

???? 使用typeid運(yùn)算符,就有可能以一個(gè)reference達(dá)到相同的執(zhí)行期代替路線(runtime

"alternative pathway"):

simplify_conv_op( const type &rt ) {if( typeid( rt ) == typeid( fct ) ){fct &rf = static_cast<fct&>( rt );// ...}else { ... } }

???? 在這里,一個(gè)明顯的較好實(shí)現(xiàn)策略是在gen和fctlasses中都引進(jìn)一個(gè)virtual function。

????? typeid運(yùn)算符傳回一個(gè)const reference,類型為type_info。在先前測(cè)試中出現(xiàn)的equlity(等

號(hào))運(yùn)算符,其實(shí)是一個(gè)被overloaded的函數(shù):

bool type_info:: operator==( const type_info& ) const;

????? 如果兩個(gè)type_info objects相等,這個(gè)equality運(yùn)算符就傳回true。

????? type_info object由什么組成?C++ Standard中對(duì)type_info的定義如下:

class type_info {public:virtual ~type_info();bool operator==( const type_info& ) const;bool operator!=( const type_info& ) const;bool before( const type_info& ) const;const char* name() const; // 傳回class原始名稱private:// prevent memberwise init and copytype_info( const type_info& );type_info& operator=( const type_info& );// data members };

?????? 編譯器必須提供的最小量信息是class的真實(shí)名稱和在type_info objects之間的某些排序算符

(這就是before()函數(shù)的目的),以及某些形式的描述器,用來表現(xiàn)eplicit class type和這一

class的任何subtypes。

?????? 雖然RTTI提供的type_info對(duì)于exception handling的支持是必要的,但對(duì)于exception

handling的完整支持而言,還不夠。如果再加上額外的一些type_info derived classes,就可以

在exception發(fā)生時(shí)提供關(guān)于指針、函數(shù)、類等等的更詳細(xì)信息。例如MetaWare就定義了以下

的額外類:

class Pointer_type_info : public type_info { ... }; class Member_pointer_info : public type_info { ... }; class Modified_type_info : public type_info { ... }; class Array_type_info : public type_info { ... }; class Func_type_info : public type_info { ... }; class Class_type_info : public type_info { ... };

????? 并允許使用者取用它們。RTTI只適用于多態(tài)類(ploymorphic classese),事實(shí)上type_info

objects也適用于內(nèi)建類型,以及非多態(tài)的使用者自定類型。這對(duì)于exception handling的支持是

有必要的。例如:

int ex_errno; ... throw ex_errno;

????? 其中int類型也有它自己的type_info object。下面就是使用方法:

int *ptr; ... if( typeid( ptr ) == typeid( int* ) )...

???? 在程序中使用typeid(expression),像這樣:

int ival; ... typeid( ival ) ...;

???? 或是使用typeid( type ),像這樣:

typeid( double ) ...;

???? 會(huì)傳回一個(gè)const type_info&。這與先前使用多態(tài)類型(polymorphic types)的差異在于,這

時(shí)候的type_info object是靜態(tài)取得,而非執(zhí)行期取得。一般的實(shí)現(xiàn)策略是在需要時(shí)才產(chǎn)生

type_info object,而非程序一開頭就產(chǎn)生之。

四、效率有了,彈性呢?

??? 傳統(tǒng)的C++對(duì)象模型提供有效率的執(zhí)行期支持。這份效率,再加上與C之間的兼容性,造成了

C++的廣泛被接受度。然而,在某些領(lǐng)域方面,像是動(dòng)態(tài)共享函數(shù)庫(dynamically shared

libraries)、共享內(nèi)存(shared memory)以及分布式對(duì)象(distrubuted object)方面,這個(gè)對(duì)

象模型的彈性還是不夠。


?

?

?????

?

?


?

?

?

?

轉(zhuǎn)載于:https://my.oschina.net/u/2537915/blog/713503

總結(jié)

以上是生活随笔為你收集整理的C++对象模型学习——站在对象模型的尖端的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網(wǎng)站內(nèi)容還不錯(cuò),歡迎將生活随笔推薦給好友。