***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 { ... }
}