問題描述:
在 C++ 面向?qū)ο缶幊讨校3龅?#8220;類型識別”的需求:已知某個(gè)對象的基類型指針,需要識別該對象的派生類型,訪問派生類型的公有函數(shù)。簡單說就是,將基類型指針轉(zhuǎn)換為派生類型指針,并用來訪問派生類型的公有函數(shù)。
問題分析:
“類型識別”本質(zhì)上是一個(gè)從抽象轉(zhuǎn)為具象的過程,破壞了對象抽象性。雖不值得提倡,但在具象化情景下,“類型識別”又是必須的。
“類型識別”常規(guī)方法是強(qiáng)制類型轉(zhuǎn)換。這需要先定義一個(gè)“對象類型”枚舉表,再在基類里定義一個(gè)返回“對象類型”的函數(shù)。這種方法的缺點(diǎn)顯而易見:
1. 需要維護(hù)全局性的“對象類型”枚舉表;
2. 向基類型構(gòu)造函數(shù)傳入“對象類型”參數(shù);
3. 每次“類型轉(zhuǎn)換”前,先要獲得“對象類型”值,判斷對象類型;
“類型識別”的另一種標(biāo)準(zhǔn)方法是RTTI。作為一項(xiàng) C++ 編譯選項(xiàng),RTTI 直接將上述常規(guī)方法教給編譯器做了。然而,這樣也是有缺點(diǎn)的:
1. RTTI會引入額外的開銷。這是可以理解的,問題是RTTI這種開銷會附加到所有類型上去;
2. RTTI要求所有庫都以RTTI方式編譯,才能正常工作。而現(xiàn)實(shí)世界總是復(fù)雜的,這一點(diǎn)未必總是能實(shí)現(xiàn);
3. 個(gè)人認(rèn)為RTTI是一種過于形式化的東西,違反了 C++ 的簡潔性、高效性原則;
4. 個(gè)人認(rèn)為RTTI的低層形式化的東西,很多時(shí)候,我們?nèi)匀恍枰x不依賴于C++類的“對象類型”枚舉表;
閱讀 WebKit 代碼時(shí),我看到了一種基于虛函數(shù)的“類型識別”方法,代碼如下:
class EventTarget {
public:
void ref() { refEventTarget(); }
void deref() { derefEventTarget(); }
virtual EventSource* toEventSource();
virtual MessagePort* toMessagePort();
virtual Node* toNode();
virtual DOMWindow* toDOMWindow();
virtual XMLHttpRequest* toXMLHttpRequest();
virtual XMLHttpRequestUpload* toXMLHttpRequestUpload();
在基類中,定義向派生類型的轉(zhuǎn)換函數(shù),并且返回為NULL。每個(gè)派生類型,重新實(shí)現(xiàn)向自身轉(zhuǎn)換的轉(zhuǎn)換函數(shù),返回自身的指針。我感覺這種方法:
1. 非常安全,使用方便;
2. 效率相對比較高;
3. 省卻了全局性的“對象類型”枚舉表,卻帶來了維護(hù)基類“轉(zhuǎn)換函數(shù)”的負(fù)擔(dān);
4. 造成了基類對派生類的依賴性,似乎不太好。
在上述方法的基礎(chǔ)上,設(shè)想了一種改進(jìn)的方法,設(shè)計(jì)原則是“從基類型向派生類型轉(zhuǎn)換的函數(shù),應(yīng)該放置在派生類中,以靜態(tài)函數(shù)的方式定義”。具體方法如下:
1. 基類定義 void* toXXXX() 和 bool isXXXX() 兩個(gè)私有虛函數(shù),用編程規(guī)范規(guī)定,禁止派生類直接訪問它們。
2. 派生類重定義上述函數(shù)。
3. 派生類型定義 XXXX* toType(Base*) 和 bool isType(Base*)兩個(gè)靜態(tài)函數(shù),內(nèi)部實(shí)現(xiàn)就是通過上述私有虛函數(shù)完成的。
4. 所有的“類型識別操作”都使用上述靜態(tài)函數(shù)完成。
問題小結(jié):
上述四種方式,很難說哪一種方式更好,宜具體情況具體對待。
我本人偏向于,使用最后一種方式作為C++編程規(guī)范和模式,因?yàn)椋且环N接口規(guī)范,其內(nèi)部實(shí)現(xiàn)可以改為前三種方法中的任何一種。至于它帶來的些許工作量,我認(rèn)為,在它成為一種工作規(guī)范后,那些少量代碼僅僅只是一點(diǎn)點(diǎn)沒有難度的機(jī)械性編程,完全可以忽略不計(jì)。如果不介意在代碼中使用宏機(jī)制,那么更可以用宏來實(shí)現(xiàn),那么只需定義編譯宏就能就在三種方式之間任意切換。
最后一種方式,體現(xiàn)了一種策略:當(dāng)你想為基類定義一些與派生類相關(guān)的工具函數(shù)時(shí),倒不如將其定義為派生類的靜態(tài)函數(shù),以避免派生類型對基類型的“污染”。