У меня есть иерархия наследования с одиночным наследованием, и я хочу проверить, является ли объект, на который указывает указатель на базу, производным типом 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, то увидим следующее:

Clang 9.0.0 в -O3

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

MSVC 19.22 в /O2 еще хуже, так как он даже не может встроить вызов сравнения 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 по замыслу вынужден делать то, что мне не нужно делать в моем конкретном контексте, например проверку нулевого указателя (и обработку ошибок с помощью исключений) или фактическое наличие структуры информации о типе в памяти. и загрузить это для сравнения.

Однако vtable_ptr не работает, если T не является конструируемым по умолчанию, может быть не таким быстрым, если оптимизатор не может скомпилировать экземпляр T, и будет вести себя неожиданным образом, если конструктор или деструктор T имеет побочные эффекты.

Вопрос в том, есть ли способ реализации template <typename T> void const * vtable_ptr(), который не требует создания экземпляра T? Эта информация, очевидно, известна компилятору. Вам просто нужно посмотреть на сборку, которую он генерирует (mov eax, offset vtable for Derived+16). Дело в том, есть ли у меня доступ к этой информации как у программиста?

0
Some programmer 12 Ноя 2019 в 13:30

1 ответ

Оператор typeid делает то, что вы хотите, если вы думаете, что можете сделать это лучше, чем это делает компилятор, то вам следует сделать свой собственный компилятор, так как «улучшение» существующих не даст вам никаких гарантий правильности

Также я думаю, что вам нужно использовать std::type_index для сравнения typeid

0
Jarod42 12 Ноя 2019 в 14:03