Неожиданный размер для виртуальных таблиц - PullRequest
0 голосов
/ 10 февраля 2020

Я изучаю наследование C ++, и поэтому я написал этот код:

// 08 Diamond Inheritance 02.cpp : Defines the entry point for the console application.
//   I'm using Microsoft Visual Studio 2015

#include <iostream>

using namespace std;

class SuperVirtual
{
public:
    int sv;
    SuperVirtual(int p1 = 11) : sv(p1)
    {
        cout << "\n SuperVirtual ctor for &" << this;
    }

    virtual void methodSuperVirtual_01()
    {
        cout << "\n Inside SuperVirtual::methodSuperVirtual_01(): sv = " << ++sv;
    }

    virtual void methodSuperVirtual_02()
    {
        cout << "\n Inside SuperVirtual::methodSuperVirtual_02(): sv = " << ++sv;
    }
};

//---------------------------------- 
class DerivedSV_01 : public SuperVirtual
{
public:
    int dsv;
    DerivedSV_01(int p1 = 21) : dsv(p1)
    {
        cout << "\n DerivedSV_01 ctor for &" << this;
    }
    virtual void methodSuperVirtual_01() override
    {
        cout << "\n Inside DerivedSV_01::methodSuperVirtual_01()";
    }
    //----------------------------------------------------
    virtual void onlyForDerivedSV_01()
    {
        cout << "\n Inside DerivedSV_01::onlyForDerivedSV_01()";
    }
};

int main()
{
    SuperVirtual sv1(1);
    sv1.methodSuperVirtual_01();

    DerivedSV_01 dsv_01;
    dsv_01.methodSuperVirtual_01();
    dsv_01.onlyForDerivedSV_01();

    return 0;
}

Я читал, что:

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

На этом этапе я предположил, что виртуальная таблица класса SuperVirtual содержит 2 элемента, в то время как виртуальная таблица для класса DerivedSV_01 содержит 3 элемента: 2 для унаследованных (и в конечном итоге ovverridden) методов + 1 для нового виртуального метода onlyForDerivedSV_01().

Однако, проверка объектов, созданных в main() с В отладчике Visual Studio я обнаружил, что:

  1. виртуальная таблица для экземпляра класса SuperVirtual содержит 2 элемента (ОК!)

  2. virtual Таблица для экземпляра класса DerivedSV_01 содержит 2 элемента ( Почему? )

Для полноты информации я предоставляю следующий снимок экрана:

image

Ответы [ 2 ]

2 голосов
/ 10 февраля 2020

Я могу воспроизвести это точно с вашим кодом в VS2019 при компиляции в режиме отладки (оптимизация компилятора отключена). Я вполне уверен, что это только проблема с отображением в окне отладчика.

locals window

Обратите внимание, что массив __vfptr отображается только в SuperVirtual, не прямо под dsv_01, поэтому, конечно, отладчик знает только о функциях в SuperVirtual vtable.

Но если вы посмотрите на столбец Value для __vfptr, вы ' Заметим, что в SuperVirtual::'vftable'[3] и DerivedSV_01::'vftable[4]' (оба отмечены зеленым) даны разные размеры массивов.

Давайте посмотрим на память в vftables (адреса vftables можно увидеть в начале столбца Value для записей __vfptr, помеченных оранжевым и красным). memory window memory window

Вы заметите, что SuperVirtual::'vftable'[3] имеет два функциональных указателя (отмечены коричневым) и nullptr. DerivedSV_01::'vftable[4]' имеет три указателя (отмечены фиолетовым и синим) и nullptr. Окно отладчика сообщает нам, какие первые две записи (также отмечены фиолетовым), но взгляните на третью запись в окне наблюдения (отмечена синим).

watch window

Отладчик говорит, что третья запись - DerivedSV_01::onlyForDerivedSV_01. Это полностью соответствует вашим (и моим) ожиданиям.

1 голос
/ 10 февраля 2020

Ответ прост: введено правило «КАК ЕСЛИ».

Компилятор выяснит, что onlyForDerivedSV_01 никогда не используется полиморфным c способом (поскольку подклассы DerivedSV_01 отсутствуют), поэтому он был преобразован в обычный метод.

Таким образом, в результате у вас есть только два виртуальных метода, унаследованных от базового класса.

Фактически при полной оптимизации компилятор должен иметь возможность удалять все виртуальные вызовы. для этого кода. Здесь вы можете увидеть все методы размещены непосредственно в функции main, и только * виртуальные вызовы выполняются на std::cout.

...