本文思路來源於http://llvm.org/docs/HowToSetUpLLVMStyleRTTI.html,敘述有不同,望諒解,希望能從其他方面幫助大家了解C++語言的底層實現。
背景
在LLVM中默認禁止了C++的RTTI特性(RTTI特性的開關-fno-rtti
),主要是為了性能考慮(C++默認的RTTI特別冗余,會使得編譯生成的文件大小增大,如果不使用RTTI反射機制的話,建議關閉。如果你對性能有極致要求的話,還可以考慮-fno-exceptions 禁用異常機制,但是關閉這兩個特性的話,需要重新編譯每一個依賴的軟件,比如最常用的libstdc++,這個工作量就比較大了
)。但是為了方便考慮,LLVM中又使用了自己手擼(hand-rolled,手卷,感覺翻譯成手擼可能比較貼合語義)的RTTI,這種特有的RTTI特性更有效而且更加靈活。當然,方便性的同時,也帶來了更多的工作量。
在這里所有的工作都在LLVM 的UserManual中有體現,在深入研究之前,要求類的書寫者(類的使用者,根本不會遇到如何實現LLVM的RTTI特性問題)了解“is-a”與“is-like-a”的關系(B繼承至A,覆蓋A的方法,B is-a A,新增方法,B is-like-a A)。
基礎
本節介紹如何設置最基本的LLVM風格的RTTI(這足以滿足99.9%的情況)。比如,我們的類繼承關系如下:
class Shape { public: Shape() {} virtual double computeArea() = 0; }; class Square : public Shape { double SideLength; public: Square(double S) : SideLength(S) {} double computeArea() override; }; class Circle : public Shape { double Radius; public: Circle(double R) : Radius(R) {} double computeArea() override; };
按照以下4步進行修改,你就可以得到一個llvm形式的RTTI:
1.添加頭文件
#include "llvm/Support/Casting.h"
2.在基類中,添加一個枚舉類型,這個枚舉類型存儲所有繼承至該類的每個類的值(其實就是枚舉編碼)
實現代碼如下:
class Shape { public: + /// Discriminator for LLVM-style RTTI (dyn_cast<> et al.) + enum ShapeKind { + SK_Square, + SK_Circle + }; +private: + const ShapeKind Kind; +public: + ShapeKind getKind() const { return Kind; } + Shape() {} virtual double computeArea() = 0; };
這里值得提的一點是,llvm風格的RTTI支持沒有v-tables(虛函數表)的類,而C++默認的dynamic_cast<>運算符並不支持這種轉換。
3.接下來,需要確保該類被初始化為與類的動態類型相對應的值。通常,您希望它是基類構造函數的參數,然后從子類構造函數傳入各自的XXXkind。
class Shape { public: /// Discriminator for LLVM-style RTTI (dyn_cast<> et al.) enum ShapeKind { SK_Square, SK_Circle }; private: const ShapeKind Kind; public: ShapeKind getKind() const { return Kind; } - Shape() {} + Shape(ShapeKind K) : Kind(K) {} virtual double computeArea() = 0; }; class Square : public Shape { double SideLength; public: - Square(double S) : SideLength(S) {} + Square(double S) : Shape(SK_Square), SideLength(S) {} double computeArea() override; }; class Circle : public Shape { double Radius; public: - Circle(double R) : Radius(R) {} + Circle(double R) : Shape(SK_Circle), Radius(R) {} double computeArea() override; };
4.有了上邊的這些代碼,還不夠,還需要一步:告訴類,自己的類型是什么,這里是通過classof方法實現的
class Shape { public: /// Discriminator for LLVM-style RTTI (dyn_cast<> et al.) enum ShapeKind { SK_Square, SK_Circle }; private: const ShapeKind Kind; public: ShapeKind getKind() const { return Kind; } Shape(ShapeKind K) : Kind(K) {} virtual double computeArea() = 0; }; class Square : public Shape { double SideLength; public: Square(double S) : Shape(SK_Square), SideLength(S) {} double computeArea() override; + + static bool classof(const Shape *S) { + return S->getKind() == SK_Square; + } }; class Circle : public Shape { double Radius; public: Circle(double R) : Shape(SK_Circle), Radius(R) {} double computeArea() override; + + static bool classof(const Shape *S) { + return S->getKind() == SK_Circle; + } };
這里已經完成了LLVM風格的RTTI(其實C++的RTTI實現也是同樣的方法,這里用法有點不准確,LLVM和MSVC的RTTI實現略有不同,大概流程是typeid,調用___RTtypeid(),判斷是否有vfptr,然后根據type_info來進行實現的,具體可以看下struct RTTIXXX那幾個結構體,感興趣的反匯編一下看看)
如何實現層次繼承的RTTI特性,大家可以關注下原文,主要是修改對應的classof,將原來的==判斷改為多個判斷,這里不進行贅述。
經驗法則
懶得翻譯了,自己看吧,很簡單,重要的是繼承樹的先序遍歷
- The
Kind
enum should have one entry per concrete class, ordered according to a preorder traversal of the inheritance tree. - The argument to
classof
should be aconst Base *
, whereBase
is some ancestor in the inheritance hierarchy. The argument should never be a derived class or the class itself: the template machinery forisa<>
already handles this case and optimizes it. - For each class in the hierarchy that has no children, implement a
classof
that checks only against itsKind
. - For each class in the hierarchy that has children, implement a
classof
that checks a range of the first child’sKind
and the last child’sKind
.