我有一个具有单继承的继承层次结构,我想检查指向基址的指针指向的对象是否完全属于派生类型 T。
我写了两种方式并比较了汇编代码:
template <typename T>
void const * vtable_ptr(T const & t)
{
return reinterpret_cast<void const * const &>(t);
}
template <typename T>
void const * vtable_ptr()
{
T t;
return vtable_ptr(t);
}
struct Base
{
virtual ~Base() = default;
};
struct Derived : Base
{
};
bool test(Base const * p)
{
return vtable_ptr(*p) == vtable_ptr<Derived>();
}
bool test2(Base const * p)
{
return typeid(*p) == typeid(Derived);
}
如果我们比较一下 test 和 test2 的汇编代码,我们可以看到如下:
-O3 处的 Clang 9.0.0
test(Base const*): # @test(Base const*)
mov eax, offset vtable for Derived+16
cmp qword ptr [rdi], rax
sete al
ret
test2(Base const*): # @test2(Base const*)
push rax
test rdi, rdi
je .LBB1_7
mov rax, qword ptr [rdi]
mov rax, qword ptr [rax - 8]
mov rdi, qword ptr [rax + 8]
mov eax, offset typeinfo name for Derived
cmp rdi, rax
je .LBB1_2
cmp byte ptr [rdi], 42
jne .LBB1_5
xor eax, eax
pop rcx
ret
.LBB1_2:
mov al, 1
pop rcx
ret
.LBB1_5:
mov esi, offset typeinfo name for Derived
call strcmp
test eax, eax
sete al
pop rcx
ret
.LBB1_7:
call __cxa_bad_typeid
/O2 的 MSVC 19.22 更糟糕,因为它甚至无法内联对 typeid 比较的调用。
bool test(Base const *) PROC ; test, COMDAT
lea rax, OFFSET FLAT:const Derived::`vftable'
cmp QWORD PTR [rcx], rax
sete al
ret 0
bool test(Base const *) ENDP ; test
p$ = 48
bool test2(Base const *) PROC ; test2, COMDAT
$LN7:
sub rsp, 40 ; 00000028H
call __RTtypeid
lea rdx, OFFSET FLAT:Derived `RTTI Type Descriptor'+8
lea rcx, QWORD PTR [rax+8]
call __std_type_info_compare
test eax, eax
sete al
add rsp, 40 ; 00000028H
ret 0
问题似乎是 typeid 在设计上被迫做我在特定上下文中不需要做的事情,例如空指针测试(以及通过异常进行错误处理)或实际上在内存中具有类型信息结构并加载进行比较。
但是,如果 T 不是默认可构造的,则 vtable_ptr 不起作用,如果优化器无法将 T 的实例化编译掉,则可能不会那么快,并且如果 T 的构造函数或析构函数具有副作用,则会以令人惊讶的方式表现。
问题是,有template <typename T> void const * vtable_ptr()
没有不需要实例化 T 的实现方式?编译器显然知道此信息。您只需要查看它正在生成的程序集 ( mov eax, offset vtable for Derived+16
)。问题是,作为程序员,我可以访问这些信息吗?