***純虛拟函數***
在設計抽象基類時,需要注意以下幾點:
(1)不要将destructor聲明為pure virtual function;
如果将destructor聲明為pure virtual function,則設計者一定得定義它。因為每一個derived class destructor會被編譯器加以擴充,以靜态調用得方式調用其“每一個virtual base class”以及“上一層base class”的destructor。
(2)不要将那些函數定義内容并不與類型有關的函數設計為virtual function,因為其幾乎不會被後繼的derived class改寫。
(3)對于其derived class可能修改某一個data member的函數,不應被聲明為const。
***“無繼承”情況下的對象構造***
先定義class Point:

class Point ... {
public:
Point(float x = 0.0, float y = 0.0) : _x(x),_y(y) ...{}
virtual float z();
protected:
float _x,_y;
} ;
你可不能小看z()這個virtual function給class Point帶來的巨大變化。virtual function的引入促使每一個class Point擁有一個vtpr,這樣一來,編譯器在constructor中添加了對vptr進行初始化的代碼,而copy constructor和copy assignment operator也會對vptr進行設定,而不再是原先簡單的bitwise操作了。
請看以下的代碼:
Point foobar()

... {
Point local;
Point *heap = new Point;
*heap = local;
delete heap;
return local;
}
将被内部轉化為:
Point foobar(Point & _result)

... {
Point local;
local.Point::Point();
Point *heap = _new(sizeof(Point));
if(heap != 0)
heap->Point::Point();
*heap = local;
_result.Point::Point(local); // copy constructor的應用
local.Point::~Point();
return;
}
從以上代碼的轉化可以看出:一般而言,如果你的設計之中有很多函數都需要以傳值方式(by value)傳回一個local class object,那麼提供一個copy constructor就比較合理。
***繼承體系下的對象構造***
假設class Point3d虛拟繼承于class Point,但由于class Point僅存在一份實體,因而class Point3d的constructor需要注意一個問題。
請看下面的繼承關系圖:

class Point3d : virtual public Point ... { ... } ;

class Vertex : virtual public Point ... { ... } ;

class Vertex3d : public Point3d, public Vertex ... { ... } ;
通常來說,class Point3d和class Vertex的constructor均需調用Point的constructor,然而,當Point3d和Vertex同為Vertex3d的subobject時,它們對Point constructor的調用操作一定不可以發生,而是交由Vertex3d來完成。
那麼如何做到這一點呢?其實隻需在constructor中添加一個參數就可以了。例如,class Vertex3d在調用Point3d和Vertex的constructor之前,總是會把參數_most_derived設為false,于是就壓制了兩個constructors中對Point constructor的調用操作。
// 在virtual base class情況下的constructor擴充内容
Vertex3d * Vertex3d::Vertex3d(Vertex3d * this , bool _most_derived, float x, float y, float z)

... {
if(_most_derived != false)
this->Point::Point(x,y);
// 在調用上一層base classes的constructor之前設定_most_derived為false
this->Point3d::Point3d(false,x,y,z);
this->Vertex::Vertex(false,x,y,z);
// 設定vptr
// 安插user code
return this;
}
為了控制一個class中有所作用的函數,編譯器隻要簡單地控制住vptr的初始化和設定操作即可。
vptr初始化操作應該如何處理?實際情況是:應該在base class constructors(具體來說,是所有的virtual base classes和上一層的base classes)調用操作之後,但是在程式員供應的碼或是“member initialization list中所列的members初始化操作”之前。為什麼是這樣呢?
如果每一個constructor都一直等待到其base class constructors執行完畢之後才設定其對象的vptr,那麼每次它都能夠調用正确的virtual function實體。
constructor的執行算法通常如下:
(1)在derived class constructor中,所有virtual base classes的constructor會被調用;
(2)在derived class constructor中,上一層base class的constructor會被調用;
(3)上述完成之後,對象的vptr(s)被初始化,指向相關的virtual table(s);
(4)如果class有member class object,而後者擁有constructor,那麼它們會以其聲明順序的相反順序被調用;
(5)使用者所定義的代碼。
下面是vptr必須被設定的兩種情況:
(1)當一個完整的對象被構造起來時,如果我們聲明一個Point對象,Point constructor必須設定其vptr;
(2)當一個subobject constructor調用了一個virtual function(無論是直接調用或間接調用)時。
在明确了哪些情況下vptr必須被設定,我們在聲明一個PVertex時,各個vptr不再需要在每一個base class constructor中被設定。解決之道是把constructor分裂為一個完整的object實體和一個subobject實體。在subobject實體中,vptr的設定可以忽略。
如果我們不對Point供應一個copy assignment operator,而光是依賴預設的memberwise copy,編譯器通常不會産生出一個實體,除非class不表現出bitwise語意。關于哪些情況class不表現出bitwise語意,請參見讀書筆記(2)。
由于在virtual base class的拷貝操作将造成subobject的多重拷貝,并且該問題至今難以解決。是以筆者的建議是:盡量不要允許一個virtual base class的拷貝操作,甚至建議:不要在任何virtual base class中聲明資料。
***解構語意學***
如果class沒有定義destructor,那麼隻有在class内含的member object(或是class自己的base class)擁有destructor的情況下,編譯器才會自動合成出一個來。
其解構順序與建構順序正好相反。
***對象的構造和解構***
一般而言,我們會把object盡可能放置在使用它的那個程式區段附近,這樣做可以節省不必要的對象産生操作和銷毀操作。
***全局對象***
全局對象的靜态初始化政策包括以下幾個步驟:
(1)為每一個需要靜态初始化的對象産生一個_sti_...()函數,内含必要的constructor調用操作或inline expansions;
(2)為每一個需要靜态的記憶體釋放操作的對象産生一個_std_...()函數,内含必要的destructor調用操作或inline expansions;
(3)在main()函數的首尾分别添加一個_main()函數(用以調用可執行檔案中的所有_sti()函數)和一個_exit()函數(用以調用可執行檔案中的所有_std()函數)。
建議根本不要用那些需要靜态初始化的全局對象。
***局部靜态對象***
假設我們有以下程式片段:

const Matrix & identity() ... {
static Matrix mat_identity;
// ...
return mat_identity;
}
此處的local static class object保證了以下語意:
(a)mat_identity的constructor必須隻能施行一次,雖然上述函數可能會被調用多次;
(b)mat_identity的destructor必須隻能施行一次,雖然上述函數可能會被調用多次。
編譯器的實際做法如下:在第一次調用identity()時把mat_identity構造出來,而在與相應檔案關聯的靜态記憶體釋放函數中将其解構。(局部靜态對象的位址在downstream component中将會被轉換到程式内用來放置global object的data segment中)
***對象數組***
如果你想要在程式中取出一個constructor的位址,這是不可以的。然而經由一個指針來激活constructor,将無法存取default argument values。那麼,如何支援以下的語句:
complex::complex( double = 0.0 , double = 0.0 );
當程式員寫出:
complex c_array[ 10 ];
時,編譯器最終需要調用:
vec_new(&c_array,sizeof(complex),10,&complex::complex,0);
為了解決這個問題,可由編譯器産生一個内部的constructor,沒有參數,在其函數内調用由程式員提供的constructor,并将default參數值明确地指定過去:
complex::complex()

... {
complex(0.0, 0.0);
}
***new和delete運算符***
以constructor來配置一個class object:
Point3d * origin = new Point3d;
被轉換為:
Point3d * origin;

if (origin = _new( sizeof (Point3d))) ... {
try ...{
origin = Point3d::Point3d(origin);
}
catch( ... ) ...{
_delete(origin); // 釋放因new而配置的記憶體
throw; // 将原來的exception上傳
}
}
如果我們配置一個數組,内帶10個Point3d objects,我們預期Point和Point3d的constructor被調用各10次,每次作用于數組中的一個元素:
// 完全不是好主意
Point * ptr = new Point3d[ 10 ];
如果我們進行如下的釋放操作:
// 隻有Point::~Point被調用
delete []ptr;
由于其觸發的vec_delete()是通過疊代走過每一個數組元素,而本例中被傳遞過去的是Point class object的大小而不是Point3d class object的大小,整個運作過程将會失敗。
解決之道在于程式層面,而非語言層面:
for ( int ix = 0 ; ix < 10 ; ix ++ )

... {
Point3d *p = &((Point3d*)ptr)[ix];
delete p;
} 當然,最好還是避免以一個base class指針指向一個derived class objects所組成的數組。
***Template的“具現”行為***
template class中的任何member都隻能通過template class的某個實體來存取或操作。
Point < float > ::Status s; // ok
Point::Status s; // error
如果我們定義一個指針,指向特定的實體,像這樣:
Point < float > * ptr = 0 ;
由于這是一個指向class object的指針,本身并不是一個class object,編譯器不需要知道與該class有關的任何members資料。是以将“Point的一個float實體”具現也就沒有必要。
如果不是一個pointer而是reference,假設:
Point < float > & ref = 0 ;
這個定義的真正語意會被擴充為:
// 内部擴充
Point < float > temp( float ( 0 ));
Point < float > & ref = temp;
以上轉化是因為reference并不是無物(no object)的代名詞,0被視作整數,必須被轉換為類型Point<float>的一個對象。
然而,member functions隻有在member functions被使用的時候,C++ Standard才要求它們被“具現”出來。這個規則的由來主要有兩個原因:
(1)空間和效率的考慮。對于未使用的函數進行“具現”将會花費大量的時間和空間;
(2)尚未實作的功能。并不是一個template具現出來的所有類型一定能夠完整支援一組member functions,因而隻需具現真正需要的member functions。
舉個例子:
Point < float > * p = new Point < float > ;
隻有(a)Point template的float執行個體、(b)new 運算符、(c)default constructor需要被“具現”。
***Template的錯誤報告***
所有與類型相關的檢驗,如果涉及到template參數,都必須延遲到真正的具現操作發生。
對于下面的template聲明:
template < class T >
class Mumble

... {
public:
Mumble(T t = 1024) : _t(t)
...{
if(tt != t)
throw ex ex;
}
private:
T tt;
}
其中像“T t = 1024”、“tt != t”這樣的潛在錯誤在template聲明時并不會報告,而會在每個具現操作發生時被檢查出來并記錄之,其結果将因不同的實際類型而不同。
Mumble < int > mi; // 上述兩個潛在錯誤都不存在
Mumble < int *> pmi; // 由于不能将一個非零的整數常量指定給一個指針,故“T t = 1024”錯誤
***Template中的名稱決議方式***
區分以下兩種意義:一種是“scope of the template definition”,也就是“定義出template”的程式,另一種是“scope of the template instantiation”,也就是“具現出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;
在“scope of the template definition”中,隻有一個foo()函數聲明位于scope之内;然而在“scope of the template instantiation”中,兩個foo()函數聲明都位于scope之内。對于以下函數操作:
// scope of the template instantiation
sr0.invariant();
那麼,在invariant()中調用的究竟是哪一個foo()函數實體呢?
Template之中,對于一個nonmember name的決議結果是根據這個name的使用是否與“用以具現出該template的參數類型”有關而決定的,如果其使用互不相關,那麼就以“scope of the template definition”來決定name,否則就以“scope of the template instantiation”來決定name。
// 因為_val的類型是int,而函數的決議隻和函數原型有關,與函數傳回值無關
// 被用來具現這個template的真正類型對于_val的類型沒有影響
_member = foo(_val);
故此處的調用操作由“scope of the template definition”來決議。
若是如下的函數調用:
sr0.type_dependent();
由于_member的類型與template參數有關,故此處由“scope of the template instantiation”來決議。
***Member Function的具現行為***
以手動方式在個别的object module中完成預先具現操作,是唯一有效率的辦法。
***執行期類型識别***
dynamic_cast運算符可以在執行期決定真正的類型。如果downcast是安全的(也就是說,一個base type pointer指向一個derived class object),這個運算符會傳回被适當轉型過的指針;如果downcast不是安全的,這個運算符會傳回0。
typedef type * ptype;
typedef fct * pfct;
simplify_conv_op(ptype pt)

... {
if(pfct pf = dynamic_cast<pfct>(pt)) ...{
...
}
else ...{ ... }
}
什麼是dynamic_cast的真正成本?pfct的一個類型描述器會被編譯器産生出來,由pt指向之class object類型描述器必須在執行期通過vptr取得。下面是可能的轉換:
// 取得pt的類型描述器
((type_info * )(pt -> vptr[ 0 ])) -> _type_description;
其中,type_info是C++ Standard所定義的類型描述器的class名稱,該class中放置着待索求的類型資訊。virtual table的第一個slot内含type_info object的位址,此type_info object與pt所指之class type有關。
dynamic_cast運算符也适用于reference身上,然而對于一個non-type-safe-cast,其結果不會與施行于指針的情況一樣。一個reference不可以像指針那樣“把自己設為0便代表了no object”;若将一個reference設為0,會引起一個臨時性對象(擁有被參考到的類型)被産生出來,該臨時對象的初值為0,這個reference然後被設定為該臨時變量的一個别名。
因而,如果reference并不真正是某一種derived class,那麼可通過丢出一個bad_cast exception進行處理:
simplify_conv_op( const type & rt)

... {
try ...{
fct &rf = dynamic_cast<fct&>(rt);
}
catch(bad cast) ...{
// ...
}
}
當然,你也可以使用typeid運算符來達到同樣的目的:
simplify_conv_op( const type & rt)

... {
if(typeid(rt) == typeid(fct))
...{
fct &rf = dynamic_cast<fct&>(rt);
}
else ...{ ... }
}