Может ли существовать пустая виртуальная таблица?

#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?
Будет ли она пустой?


person Aquarius_Girl    schedule 21.10.2011    source источник
comment
Примечание. Стандарт C++ на самом деле не говорит, что виртуальные функции вообще должны быть реализованы с помощью виртуальной таблицы.   -  person Flexo    schedule 21.10.2011
comment
есть ли какая-то особая причина для этого вопроса?   -  person Philipp    schedule 21.10.2011
comment
Если ваша реализация соответствует ABI Itanium C++, сразу приступайте!   -  person Lightness Races in Orbit    schedule 21.10.2011


Ответы (4)


Это полностью зависит от компилятора. Когда я принудительно создаю экземпляры 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)
person NPE    schedule 21.10.2011
comment
Я тоже собирался включить это в свой ответ, но я думаю, что вы справились с этим лучше, чем я. - person Flexo; 21.10.2011
comment
спасибо, а что это за информационная структура, кстати? Что он содержит? Означает тип функции или что-то в этом роде? - person Aquarius_Girl; 21.11.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 в обоих конструкторах, по сути одно и то же.

person Flexo    schedule 21.10.2011

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

person mukeshkumar    schedule 21.10.2011
comment
Не тот случай, когда я компилирую простой пример, используя g++. Смотрите мой ответ о том, что я узнал. - person NPE; 21.10.2011

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

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

person David Rodríguez - dribeas    schedule 21.10.2011