对于处于正常运行流程中的RTTI需求,C++内置的RTTI可能引入不必要的开销,因此我们参考LLVM的实现,实现了一套RTTI机制。
为使现有的类支持我们的RTTI接口,其需要通过如下几种方式之一(但不应同时)向框架提供必要的信息:
如果向下转换时始终转换到精确匹配的运行时类型(即向下转换时从不转换到某个对象的父类),那么可以让T
从ExactMatchCastable
继承,并在构造时调用SetRuntimeType(kRuntimeType<T>)
即可。SetRuntimeType
通过ADL找到,不应当通过flare::
指定。
这种情况下如果需要向下转换至实际运行时类型的基类,依然可以通过dynamic_cast
,或者对转换目标(运行时类型的基类)特化CastingTraits<T>
实现。但是这种用法目前不受支持,如果有这种任意转换的需求,我们建议使用更加通用的其他方法。
每个子类均提供原型如bool T::classof(const Base& val)
的静态方法。这一方法应当使用类型自身所提供(框架不感知)的途径,来识别val
实际类型是否为T
或其子类。对于某些场景,可能可以通过继承Castable
基类,通过其提供的SetRuntimeType
、GetRuntimeType
、kRuntimeType<T>
来简化实现。
特化CastingTraits<T>
,并实现bool RuntimeTypeCheck<U>(const U& val)
。其行为同T::classof
。
第二种方式侵入型较强但编码简单,第三种方式优缺点相反。
对于编译期即可确定可行的向上转换(即子类转换为基类的情况),框架会进行优化,这种情况classof
无需处理(因此基类不需要提供classof
)。
这儿我们通过给基类增加type
字段来实现运行时识别类型,取决于具体的场景,用户也可以通过其他方式(如虚函数等)来识别运行时类型。
相对来说,增加字段来识别运行时类型提供了更多的优化空间,通过被内联的accessor(如这儿示例)访问type
可以被优化为单条mov
指令;而虚函数通常可能会引入难以优化(devirtualization)的虚函数调用。
ExactMatchCastable
实现struct C1 : ExactMatchCastable {};
struct C2 : C1 {
C2() { SetRuntimeType(this, kRuntimeType<C2>); }
};
struct C3 : C1 {
C3() { SetRuntimeType(this, kRuntimeType<C3>); }
};
void CastTypes() {
auto pc2 = std::make_unique<C2>();
C1* p = pc2.get();
ASSERT_NE(nullptr, dyn_cast<C1>(p)); // Success.
ASSERT_NE(nullptr, dyn_cast<C2>(p)); // Success.
ASSERT_EQ(nullptr, dyn_cast<C3>(p)); // Failure.
}
classof
实现struct Base {
enum { kA, kB, kC } type;
};
struct A : Base {
A() { type = kA; }
static bool classof(const Base& val) {
return val.type == kA || val.type == kB;
}
};
struct B : A {
B() { type = kB; }
static bool classof(const Base& val) { return val.type == kB; }
};
struct C : Base {
C() { type = kC; }
static bool classof(const Base& val) { return val.type == kC; }
};
void CastTypes() {
auto pb = std::make_unique<B>();
ASSERT_NE(nullptr, dyn_cast<Base>(pb.get())); // Success.
ASSERT_NE(nullptr, dyn_cast<A>(pb.get())); // Success.
ASSERT_NE(nullptr, dyn_cast<B>(pb.get())); // Success.
ASSERT_EQ(nullptr, dyn_cast<C>(pb.get())); // Failure.
}
我们提供了如下一些方法供使用:
bool isa<T>(const U& val)
:判定val
的运行时类型是否为T
或其子类。T* dyn_cast<T>(U& val)
/ T* dyn_cast<T>(U* val)
:如果val
运行时类型是T
或其子类,则将之转换为T*
,否则返回nullptr
。dyn_cast
不能接受nullptr
。T* cast<T>(U& val)
/ T* cast<T>(U* val)
:将val
转换为T*
。如果val
的运行时类型不是T
或其子类,则会导致CHECK
失败(即崩溃)。cast
不能接受nullptr
。
T* (dyn_)cast_or_null(...)
:同对应的(dyn_)cast
,但可以接受nullptr
(并在这种情况下返回nullptr
)。LLVM的接口设计与dynamic_cast
有一些不同。我们经过分析,认为LLVM的选择是合理的,并采取了相同的设计:
(dyn_)cast
默认不支持nullptr
。
dynamic_cast
显式允许传入nullptr
,但是需要转换可能为nullptr
的情况实际上较少,因此我们单独提供了..._or_null
版本处理这种情况。
模板参数只需要提供具体类型T
,而非指针或引用类型。
dynamic_cast
接受T*
或T&
并:
nullptr
(既失败符合预期)或抛出异常(即失败属于错误)。dynamic_cast
的这一设计存在几方面问题:
dynamic_cast
的dynamic_pointer_cast
接受的是T
,而非xxx_pointer<T>
(即,最终返回类型)。T
vs T*
/ T&
)。另外,只接受参数T
允许我们始终返回T*
,这更符合我们的编码规范(不使用非常量引用,只使用非常量指针)。
针对转换是否失败符合预期,分别提供dyn_cast
或cast
。dynamic_cast
通过T*
或T&
区分,
我们的分析见上。
我们在Xeon Gold 6133上的测试代码得到如下数据:
Benchmark Time CPU Iterations
--------------------------------------------------------------------------
Benchmark_BuiltinDynamicCast 22 ns 22 ns 31094959
Benchmark_DynCast 2 ns 2 ns 419860704
Benchmark_ExactMatchCastableDynCast 2 ns 2 ns 349577035
我们的benchmark框架空转开销约2ns,因此dyn_cast
本身的开销应当小于1ns。
我们分析了编译产出,确认dyn_cast
的调用没有被优化掉,并编译为如下代码:
... <+48>: mov (%rax),%rax
... <+51>: cmpl $0x2,0x8(%rax)
... <+55>: cmovae %r12,%rax
... <+59>: mov %rax,(%rdx)
Вы можете оставить комментарий после Вход в систему
Неприемлемый контент может быть отображен здесь и не будет показан на странице. Вы можете проверить и изменить его с помощью соответствующей функции редактирования.
Если вы подтверждаете, что содержание не содержит непристойной лексики/перенаправления на рекламу/насилия/вульгарной порнографии/нарушений/пиратства/ложного/незначительного или незаконного контента, связанного с национальными законами и предписаниями, вы можете нажать «Отправить» для подачи апелляции, и мы обработаем ее как можно скорее.
Опубликовать ( 0 )