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

Итак, я знаю, что в c ++ виртуальные методы для каждого класса хранятся в таблице, и у каждого экземпляра есть указатель, который указывает на эту таблицу. Итак, мой вопрос, как выглядит таблица подклассов. Я предоставлю директиву на ассемблере:

vtable for Derived:
    .long   0
    .long   _typeinfo for Derived
    .long   _Derived::set()
    .globl  _vtable for Base
    .section    .rdata$vtable for Base,"dr"
    .linkonce same_size
    .align 4

Итак, отсюда я вижу, что у Derived есть один виртуальный метод, который set(), но часть, которая вызывает ошибки, vtable для Base. Содержит ли Derived vptr указатель на Base vptr или он хранится внутри vtable Derived. Я заметил, что в компиляторе кода просто хранится vtable по адресу 0 объекта, один раз в конструкторе Base и один раз в Derived. Почему vtable не перезаписывается?

PS Я не совсем понимаю директивы

РЕДАКТИРОВАТЬ:

class Base{
   virtual void print() {
       printf("Base");
   }
}

class Derived : Base{
   virtual void print(){
       printf("Derived");
   }
}

1 Ответ

3 голосов
/ 27 марта 2020

Простой ответ: это зависит от компилятора. Стандарт не определяет, как это должно быть реализовано, только как он должен себя вести.

На практике реализации, как правило, просто имеют производный vtable, который начинается с той же структуры, что и базовый vtable, чем добавляет производный класс. методы в конце (то есть новые методы в производном классе, а не переопределения).

Указатель vtable просто указывает начало всей таблицы. Если доступ к объекту осуществляется через базовый тип указателя, то никто не должен заглядывать за пределы методов базового класса.

Если указатель относится к производному типу, то этот же указатель позволит получить доступ далее вниз по таблице к виртуальным методам, объявленным в производном классе.

ADDENDUM: множественное наследование

Множественное наследование следует тем же самым базовым c понятиям, но быстро становится сложным по очевидным причинам. Но есть одна важная особенность, о которой следует помнить.

У многократно производного класса есть один указатель vtable для каждого его базовых классов, указывающий на разные vtables или разные места в одной vtable (реализации) зависимый).

Но важно помнить, что это один для непосредственного базового класса для объекта .

Таким образом, если у вас был класс с несколькими производными от одного int данных и три непосредственных базовых класса, размер каждого объекта на самом деле будет 16 байтов (в 32-битной системе; больше в 64-битной). 4 для int и четыре для каждого из указателей vtable. Плюс, конечно, размеры каждого из самих базовых классов.

Это означает, что в C ++ интерфейсы недешевы. (Очевидно, что в C ++ нет настоящих интерфейсов, но базовый класс без данных и только виртуальные методы имитируют его.) Каждый такой интерфейс стоит размер указателя на объект .

В таких языках, как C# и Java, где интерфейсы являются частью языка, существует немного другой механизм, посредством которого все интерфейсы маршрутизируются через один указатель vtable. Это немного медленнее, но означает только один указатель vtable на объект, однако реализовано много интерфейсов.

Я бы по-прежнему следовал подходу стиля интерфейса в C ++ по соображениям дизайна, но всегда буду помнить об этих дополнительных издержках.

(И ничего из этого даже не касается виртуального наследования.)

...