Может ли существовать пустая виртуальная таблица? - PullRequest
3 голосов
/ 21 октября 2011
#include <iostream>
using namespace std;

class Z
{
public:
    int a;
    virtual void x () {}
};

class Y : public Z
{
public:
    int a;
};

int main() 
{
    cout << "\nZ: "  << sizeof (Z);
    cout << "\nY: "  << sizeof (Y);
} 

Поскольку Y наследует Z, у него также будет виртуальная таблица. Хорошо. Но у него нет никаких виртуальных функций, так что же будет содержимым виртуальной таблицы Y?
Это будет пусто?

Ответы [ 4 ]

4 голосов
/ 21 октября 2011

Это полностью зависит от компилятора. Когда я запускаю создание экземпляров Y и Z, g++ 4.4.5 создает две разные виртуальные таблицы для Y и Z, которые имеют одинаковый размер .

Обе таблицы указывают на один и тот же x(), но указывают на разные typeinfo структуры:

;=== Z's virtual table ===
_ZTV1Z:
        .quad   0
        .quad   _ZTI1Z     ; Z's type info
        .quad   _ZN1Z5xEv  ; x()

_ZTI1Z:
        ; Z's type info (omitted for brevity)

;=== Y's virtual table ===
_ZTV1Y:
        .quad   0
        .quad   _ZTI1Y     ; Y's type info
        .quad   _ZN1Z5xEv  ; x()

_ZTI1Y:
        ; Y's type info (omitted for brevity)
3 голосов
/ 21 октября 2011

В примере, который вы разместили, GCC по умолчанию полностью оптимизирует vtable. Поскольку это только одна единица перевода и все видно, это возможно.

Я изменил ваш пример на:

#include <iostream>
using namespace std;

class Z
{
public:
    int a;
    virtual void x () const {}
};

class Y : public Z
{
public:
    int a;
};

int main()
{
    Y y;
    const Z& z1=y;
    const Z& z2=Z();
    z1.x(),z2.x();
    cout << "\nZ: "  << sizeof (Z);
    cout << "\nY: "  << sizeof (Y);
}

В этом случае vtable генерируется на выходе:

nm a.out|c++filt|grep -i vtable
08048880 V vtable for Y
08048890 V vtable for Z
0804a040 V vtable for __cxxabiv1::__class_type_info@@CXXABI_1.3
0804a120 V vtable for __cxxabiv1::__si_class_type_info@@CXXABI_1.3

Если мы сгенерируем сборку с -S, тогда мы сможем найти конструкторы (искаженные как _ZN1ZC2Ev и _ZN1YC2Ev соответственно в моей системе). Они занимаются настройкой виртуальных таблиц (_ZTV1Z и _ZTV1Y):

Конструктор для Z:

_ZN1ZC2Ev:
.LFB970:
        .cfi_startproc
        pushl   %ebp
        .cfi_def_cfa_offset 8
        .cfi_offset 5, -8
        movl    %esp, %ebp
        .cfi_def_cfa_register 5
        movl    8(%ebp), %eax
        movl    $_ZTV1Z+8, (%eax)
        popl    %ebp
        .cfi_def_cfa 4, 4
        .cfi_restore 5
        ret

А Y:

_ZN1YC2Ev:
.LFB972:
        .cfi_startproc
        pushl   %ebp
        .cfi_def_cfa_offset 8
        .cfi_offset 5, -8
        movl    %esp, %ebp
        .cfi_def_cfa_register 5
        subl    $24, %esp
        movl    8(%ebp), %eax
        movl    %eax, (%esp)
        call    _ZN1ZC2Ev
        movl    8(%ebp), %eax
        movl    $_ZTV1Y+8, (%eax)
        leave
        .cfi_restore 5
        .cfi_def_cfa 4, 4
        ret

Интересно то, что то, что помещается в vtable в обоих конструкторах, по сути одинаково.

1 голос
/ 21 октября 2011

В общих реализациях компилятора виртуальная таблица Y будет иметь те же записи, что и Z

0 голосов
/ 21 октября 2011

Подробные сведения уже были добавлены в других ответах, но с точки зрения очень высокого уровня вы должны думать, что производный тип Y имеет все унаследованные виртуальные функции от Z, он просто не обеспечивает переопределение для любого из них (ну, для одного).

Вся таблица виртуальных функций Идея состоит в том, что все типы, производные от этой базы, будут иметь совместимую таблицу. Когда компилятору нужно найти конкретную реализацию виртуального метода для вызова, он знает, что он может зависеть от присутствующей таблицы, а элемент таблицы является указателем на final-overrider этот метод для этого конкретного объекта, даже если этот final-overrider оказывается первым и единственным переопределителем согласно вашему примеру.

...