Подтвердить что ты не робот

Детали реализации виртуальной диспетчеризации

Прежде всего, я хочу пояснить, что понимаю, что нет понятия vtables и vptrs в стандарте С++. Однако я думаю, что практически все реализации реализуют механизм виртуальной диспетчеризации практически так же (исправьте меня, если я ошибаюсь, но это не главный вопрос). Кроме того, я считаю, что знаю, как работают виртуальные функции, то есть я всегда могу сказать, какую функцию вызывать, мне просто нужны детали реализации.

Предположим, кто-то спросил меня следующее:
  "У вас есть базовый класс B с виртуальными функциями v1, v2, v3 и производным классом D: B, который переопределяет функции v1 и v3 и добавляет виртуальную функцию v4. Объясните, как работает виртуальная диспетчеризация".

Я бы ответил так:
Для каждого класса с виртуальными функциями (в данном случае B и D) мы имеем отдельный массив указателей на функции, называемый vtable.
Vtable для B будет содержать

&B::v1
&B::v2
&B::v3

vtable для D будет содержать

&D::v1
&B::v2
&D::v3
&D::v4 

Теперь класс B содержит указатель на элемент vptr. D естественно наследует его и, следовательно, содержит его тоже. В constuctor и деструкторе B B устанавливает vptr, чтобы указать на B vtable. В конструкторе и деструкторе D D он указывает на D vtable.
Любой вызов виртуальной функции f объекта x полиморфного класса X интерпретируется как вызов x.vptr [f position in vtables]

Вопросы:
1. Есть ли ошибки в приведенном выше описании?
2. Как компилятор знает позицию f в таблице vtable (подробно, пожалуйста)
3. Означает ли это, что если класс имеет две базы, то он имеет два vptrs? Что происходит в этом случае? (попробуйте описать так же, как я, насколько это возможно)
4. Что происходит в алмазной иерархии с A сверху B, C в середине и D внизу? (A - виртуальный базовый класс B и C)

Спасибо заранее.

4b9b3361

Ответ 1

1. У меня есть ошибки в приведенном выше описании?

Все хорошо.: -)

2. Как компилятор знает позицию f в таблице vtable

У каждого поставщика будет свой собственный способ сделать это, но я всегда думаю о том, что vtable является отображением символа функции-члена в смещение памяти. Поэтому компилятор просто поддерживает этот список.

3. Означает ли это, что если класс имеет две базы, то он имеет два vptrs? Что происходит в этом случае?

Как правило, компиляторы составляют новую vtable, которая состоит из всех vtables виртуальных баз, добавленных вместе в том порядке, в котором они были указаны, вместе с указателем vtable виртуальной базы. Они следуют этому с помощью функций vtable класса-получателя. Это особенно специфично для поставщика, но для class D : B1, B2 вы обычно видите D._vptr[0] == B1._vptr.

multiple inheritance

Это изображение фактически предназначено для компоновки полей-членов объекта, но vtables может быть составлено компилятором точно так же (насколько я понимаю).

4. Что происходит в алмазной иерархии с A сверху B, C посередине и D внизу? (A - виртуальный базовый класс B и C)

Короткий ответ? Абсолютный ад. Вы фактически наследовали обе базы? Только один из них? Никто из них? В конечном счете, используются одни и те же методы составления таблицы vtable для класса, но как это делается, он варьируется в зависимости от того, как это должно быть сделано, вовсе не каменное. Существует достойное объяснение решения проблемы иерархии бриллиантов здесь, но, как и большинство из этого, он довольно специфичен для вендора.

Ответ 2

  • Выглядит хорошо для меня.
  • Реализация специфична, но большинство из них только в порядке исходного кода - это означает, что порядок, который они отображаются в классе, начинается с базового класса, а затем добавляет новые производные от виртуальных функций. Пока компилятор имеет детерминированный способ сделать это, то все, что он хочет сделать, прекрасно. Однако в Windows для создания COM-совместимых V-таблиц он должен быть в порядке источника

  • (не уверен)

  • (гадание) Алмаз просто означает, что у вас может быть две копии базового класса B. Виртуальное наследование объединит их в один экземпляр. Поэтому, если вы установите член через D1, вы можете прочитать его через D2. (с C, полученным из D1, D2, каждый из которых получен из B). Я считаю, что в обоих случаях vtables были бы идентичны, так как указатели на функции одинаковы - память для членов данных - это то, что слилось.

Ответ 3

Комментарии:

  • Я не думаю, что в него входят деструкторы!

  • Вызов, такой как, например, D d; d.v1();, вероятно, не будет реализован через vtable, поскольку компилятор может разрешить адрес функции в момент компиляции/ссылки.

  • Компилятор знает позицию f, потому что она там была!

  • Да, класс с несколькими базовыми классами, как правило, имеет несколько vptrs (предполагая виртуальные функции в каждом базовом классе).

  • "Эффективные книги C++" Скотта Мейерса объясняют множественное наследование и бриллианты лучше, чем я могу; Я бы рекомендовал прочитать их по этой (и многим другим) причинам. Подумайте о том, что они важны для чтения!