C++ RTTI库设计

RTTI 是 Runtime type information 的缩写, 是指在运行时保存对象的类型信息, 一般是用来描述C++的一种特性. C++本身提供了 dynamic_cast 和 typeid 运算符来实现运行时类型识别, 但是 dynamic_cast的 效率往往较低, 所以实现了一个较高性能的 RTTI 库.

dynamic_cast的“运行时类型的转换匹配”,是通过维护一棵由type_info类型对象作为节点的类型继承关系的树,遍历这棵继承树来确定一个待转换的对象的类型和目标类型之间是否存在 “is-a” 关系.

于是我们的思路就是参考dynamic_cast的思路, 通过保存在继承链的类型信息, 然后线性查找来判断是否存在 “is-a” 关系.

根据 C++ 继承初始化顺序的特性, 我们可以通过让继承链上的每一个类都继承我们的协议类, 在协议类中保存每个类型的信息, 那么用什么来表示一类类型呢? 模版类 + 静态成员, 模版类实例化后每个特化的类都会持有同一个针对该类型特化的类的静态成员变量, 我们可以用该静态成员变量的地址来表示该类型.

接着还有一个问题, 我们的思路在多继承时会产生菱形继承问题, 这个比较简单, 用虚继承就可以, 接着会发现, 我们通过虚继承巧妙的将协议类中的成员实现了持久使用, 并且继承链上的每个类都保存了该继承类的类型信息.

为了简单起见, 可以使用使用数组来保存类型信息以及对应类型的对象地址. 因为虚继承的特性, 我们设计一个基类作为底层, 然后功能类虚继承基类, 通过继承的增长来实现下标的偏移将所需信息保存在数组中.

reinterpret_cast 是为了将 id 地址的位模式重新解释, 具体使用方式见: reinterpret_cast 转换

实现如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
class rtti {
protected:
std::array<std::intptr_t, RTTI_CHAIN_MAX_SIZE> inherChainID;
std::array<void*, RTTI_CHAIN_MAX_SIZE> inherChainPtr;

int inherChainCounter;
rtti(): inherChainCounter(-1) {
for (auto& id : inherChainID) id = -1;
for (auto& ptr : inherChainPtr) ptr = nullptr;
}
public:

virtual ~rtti() {}
inline void* getPtrKindOf(std::intptr_t type) {
for (int i = 0; i <= inherChainCounter; ++i) {
if (inherChainID[i] == type) {
return inherChainPtr[i];
}
}
return nullptr;
}
inline bool isKindOf(std::intptr_t type) {
return getPtrKindOf(type) ? true : false;
}
};

template <typename T>
class rttiport: public virtual rtti {
private:
static std::intptr_t id;
public:
static std::intptr_t type();
protected:
rttiport() {
++inherChainCounter;
inherChainID[inherChainCounter] = type();
inherChainPtr[inherChainCounter] = static_cast<T*>(this);
}
virtual ~rttiport() {}
};
template <typename T>
std::intptr_t rttiport<T>::id(0);

template <typename T>
std::intptr_t rttiport<T>::type() {
return reinterpret_cast<std::intptr_t>(&id);
}

为了使用方便, 还需要实现两个小函数, 来方便使用.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
template <typename T>
static T* cast(rtti* to) {
if (to == nullptr) {
return nullptr;
}
return static_cast<T*> (to->getPtrKindOf(rttiport<T>::type()));
}

template <typename T>
static bool is(rtti* obj) {
if (obj == nullptr) {
return false;
}
return obj->getPtrKindOf(rttiport<T>::type()) != nullptr;
}

cast 函数是将 to 指向的对象转换为 T 类型.

is 函数是为了更方便的进行类型识别, 判断 obj指向对象的类型和 T 是否相同.

script>