Детали реализации виртуальной отправки - PullRequest
31 голосов
/ 20 октября 2010

Прежде всего, я хочу прояснить, что Я понимаю, что в стандарте C ++ нет понятия 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, естественно, наследует его и поэтому тоже содержит. В конструкторе и деструкторе B B устанавливает vptr для указания на vtable таблицы B. В конструкторе и деструкторе D D устанавливает его так, чтобы он указывал на vtable таблицы D.
Любой вызов виртуальной функции f для объекта x полиморфного класса X интерпретируется как вызов x.vptr [позиция f в vtables]

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

Заранее спасибо.

Ответы [ 3 ]

36 голосов
/ 20 октября 2010

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 для класса, но то, как это делается, сильно варьируется, поскольку как это должно быть сделано, совсем не изложено в камне. Здесь есть приличное объяснение решения проблемы алмазной иерархии здесь , но, как и большая часть этого, она в значительной степени зависит от поставщика.

5 голосов
/ 20 октября 2010
  1. выглядит хорошо для меня
  2. Специфично для реализации, но большинство из них просто в порядке исходного кода - то есть в порядке их появления в классе - начиная с базового класса, затем добавляя новые виртуальные функции из производных. Пока компилятор имеет детерминистский способ сделать это, тогда все, что он хочет сделать, прекрасно. Однако в Windows для создания COM-совместимых V-таблиц он должен быть в исходном порядке

  3. (не уверен)

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

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

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

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

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

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

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

...