Как одна виртуальная таблица отслеживает новые виртуальные функции?

Я использую VS 2013 и пытаюсь увидеть, как vptr и vftable работают на уровне объекта. Итак, у меня есть следующие классы:

#include<iostream>
using namespace std;

class baseClass
{
public:
    void nonVirtualFunc() {}
    virtual void virtualNonOverriddenFunc() {}
    virtual void virtualOverriddenFunc() {}
};

class derivedClass : public baseClass
{
public:
    virtual void virtualOverriddenFunc() {}
    virtual void derivedClassOnlyVirtualFunc() { cout << "derivedClass" << endl; }
};


int main(int argc, char** argv) {

    derivedClass derivedClassObj2;
    cout << "Size of derivedClassObj: " << sizeof(derivedClassObj2) << endl;

    return 0;
}

И вот что я вижу при отладке: введите здесь описание изображения

Теоретически должно быть ДВА vptrs. Один для vftable базового класса и один для производного класса для отслеживания недавно добавленного производного классаOnlyVirtualFunc().

Но, как видите, есть только один vptr/vftable. Но механизм работает исправно.

Я думал, что есть второй vptr, который я не вижу в окне просмотра, поэтому я распечатал размер объекта. Это 4 байта, указывающие, что присутствует только один указатель.

Итак, как это работает с недавно добавленной виртуальной функцией?

Согласно это должно быть два vptrs.

РЕДАКТИРОВАТЬ: я проверил содержимое памяти vftable, как предложил Серж, и действительно есть три записи. введите здесь описание изображенияПо какой-то причине оно не отображается в отладчике.

Ваше здоровье.


person madu    schedule 01.12.2014    source источник
comment
Нет, потому что при создании производного объекта записи в vtable заменяются адресами виртуальных функций производного (если производный переопределяет их). Это делается во время создания объекта, что объясняет, почему вы не должны вызывать виртуальные функции в конструкторах/деструкторах.   -  person Borgleader    schedule 01.12.2014
comment
Почему в объекте должно быть два? Одного достаточно, производные классы vtable основаны на базовых классах, добавляя дополнительные записи для большего количества виртуальных и заменяя другие, чтобы указать на новую реализацию.   -  person Deduplicator    schedule 01.12.2014
comment
@Borgleader: вызов виртуальных функций в ctors и dtors — это нормально.   -  person Deduplicator    schedule 01.12.2014
comment
@Deduplicator ... при условии, что вы точно понимаете, какие переопределения вызываются, что не все понимают.   -  person Angew is no longer proud of SO    schedule 01.12.2014
comment
Все просто, типа кто ctor/dtor просто выполняет. (Остерегайтесь там ctor-init-list.)   -  person Deduplicator    schedule 01.12.2014
comment
Но в производный класс добавлена ​​НОВАЯ виртуальная функция. Где вход для этого виртуального? Это мое замешательство.   -  person madu    schedule 01.12.2014
comment
@madu Может быть, отладчик играет с вами злую шутку, показывая только базовую часть vtable. Вся виртуальная таблица обязательно будет иметь добавленную к ней запись для новой функции.   -  person Angew is no longer proud of SO    schedule 01.12.2014
comment
@Angew Да, я так и думал. Но когда я распечатываю размер объекта, он составляет 4 байта. Это то же самое, что и при наличии только одного vptr.   -  person madu    schedule 01.12.2014
comment
@Griwes: существует виртуальная отправка, и притворяться, что это не так, является потенциально фатальной ошибкой. Иерархия классов ограничена ниже целевого наиболее производного типа.   -  person Deduplicator    schedule 01.12.2014
comment
@Deduplicator есть идеи, как я могу просмотреть полную vftable?   -  person madu    schedule 01.12.2014
comment
@madu Есть только один vptr, но ваш отладчик отображает только две записи из таблицы для базового класса. Из верхнего ответа на вопрос, который вы связали: на самом деле компиляторы объединяют разные виртуальные таблицы в одну виртуальную таблицу.   -  person molbdnilo    schedule 01.12.2014
comment
@madu Да, есть только один vptr. Указание на vtable из 3 элементов. Возможно, отладчик как-то запутался и показывает только первые 2 элемента.   -  person Angew is no longer proud of SO    schedule 01.12.2014
comment
@Deduplicator, да, я преувеличил. Существует виртуальная отправка, но если ваш вариант использования не является абсурдно безумным (например, здесь), виртуальный диспетчер ведет себя так, как будто его на самом деле не существует (пока ваш вариант использования не является абсурдно безумным, компилятор может точно видеть, какие функции вы вызываете статически). (Хорошо, если исключить из объяснения абсурдно безумные случаи.)   -  person Griwes    schedule 01.12.2014
comment
Что касается самого вопроса: я не понимаю, как это может потенциально соответствовать c++; в языке нет понятия vtables или vptrs.   -  person Griwes    schedule 01.12.2014
comment
Отладчик показывает только две записи, потому что вы просматриваете подобъект baseClass, а виртуальная таблица baseClass содержит только две записи.   -  person Raymond Chen    schedule 02.12.2014
comment
@RaymondChen есть идеи, как я могу посмотреть новую запись виртуальной функции? Спасибо.   -  person madu    schedule 02.12.2014
comment
Используйте окно памяти. Отладчик не сильно вам поможет, потому что это внутренние детали, на которые вам не следует полагаться.   -  person Raymond Chen    schedule 02.12.2014
comment
@RaymondChen Спасибо. Да, я наконец нашел это в окне памяти, как предложил Серж. Хотелось бы, чтобы это было более очевидно.   -  person madu    schedule 02.12.2014


Ответы (1)


Реализация vtable зависит от компилятора. Размер объекта (4 байта) показывает, что виртуальная таблица не реплицируется в объекте, поскольку 4 байта — это всего лишь один указатель. Мое понимание:

  • существует одна и только одна vtable(*) для каждого класса (не для каждого объекта)
  • у каждого объекта есть указатель на его vtable (один из его фактического класса)
  • поскольку _vfptr является атрибутом класса-предка, отладчик показывает его в классе-предке и поэтому показывает только методы, определенные в этом классе.

Но наверняка настоящая _vtable содержит записи для других виртуальных методов... после записей, отображаемых отладчиком!

(*) Все становится сложнее, когда вы думаете о внутренней организации массива _vfptr. На самом деле его можно рассматривать как содержащий копии всех виртуальных таблиц классов-предков. Здесь две первые записи derivedClass соответствуют виртуальной таблице baseClass. Но если вы откроете окно Память и проверите, что находится по адресу _vfptr (в вашем примере 0x00d9ba68), вы должны увидеть третью запись перед нулевой записью (по крайней мере, это то, что показывает мой MSVC Express 2008). ). Эта третья запись соответствует функции derivedClassOnlyVirtualFunc, но не отображается отладчиком, как я сказал выше.

person Serge Ballesta    schedule 01.12.2014
comment
1. Даже существование виртуальной таблицы (если она есть) является деталью реализации. 2. Взгляните на std::iostream. По моим подсчетам, у него 3 виртуальных таблицы, потому что каждый экземпляр имеет 3 указателя на vtable. (О некоторых распространенных реализациях.) 3. Ни базовые типы, ни классы без виртуальных баз и членов не имеют vtable и vtable-указателя. - person Deduplicator; 01.12.2014
comment
@Серж Спасибо. Я посмотрел содержимое памяти, и похоже, что третьей записи нет. Я обновил исходный вопрос скриншотом памяти. - person madu; 02.12.2014
comment
@Serge Ты прав. Есть вход. Я сделал ошибку в коде. Спасибо тебе за это. Странно, почему не появляется. - person madu; 02.12.2014