Виртуальное наследование в C ++ - PullRequest
3 голосов
/ 04 июля 2010

Я читал Википедию статью о виртуальном наследовании. Я следил за всей статьей, но не мог действительно следовать последнему абзацу

Это реализуется путем предоставления Млекопитающее и крылатый животный с vtable указатель (или «vpointer»), так как, например, смещение памяти между начало млекопитающего и его Животная часть неизвестна до времени выполнения. Таким образом летучая мышь становится (Vpointer, Млекопитающее, vpointer, WingedAnimal, Bat, животных). Есть два vtable указателя, один на иерархия наследования, которая практически наследует животных. В этом примере один для млекопитающего и один для крылатого животного. Следовательно, размер объекта увеличился на два указателя, но теперь есть только одно животное и нет неоднозначность. Все объекты типа Bat будет иметь одинаковые vpointers, но каждый Объект Bat будет содержать свой собственный уникальный Животный объект. Если другой класс наследуется от млекопитающих, таких как Белка, потом впойнтер в Млекопитающим в Белке будет отличается от впойнтера в Млекопитающее в летучей мыши, хотя они все еще может быть по сути то же самое в особый случай, что белка часть объекта имеет одинаковый размер как часть летучей мыши, потому что тогда расстояние от млекопитающего до животного часть такая же. Vtables не являются действительно то же самое, но все необходимое информация в них (расстояние) составляет.

Может кто-нибудь, пожалуйста, пролить немного света на это.

Ответы [ 3 ]

6 голосов
/ 04 июля 2010

Иногда вам просто нужно увидеть некоторые коды / схемы :) Обратите внимание, что в Стандарте нет упоминаний об этой детали реализации.

Прежде всего, давайте посмотрим, как реализовать методы в C ++:

struct Base
{
  void foo();
};

Это похоже на:

struct Base {};

void Base_foo(Base& b);

И на самом деле, когда вы смотрите на вызов метода в отладчике, вы часто видите аргумент this в качестве первого параметра. Иногда его называют неявным параметром.

Теперь перейдем к виртуальной таблице. В C и C ++ можно использовать указатели для работы. Vtable - это, по сути, таблица указателей на функции:

struct Base
{
  int a;
};

void Base_set(Base& b, int i) { b.a = i; }
int Base_get(Base const& b) { return b.a; }

struct BaseVTable
{
  typedef void (*setter_t)(Base&, int);
  typedef int (*getter_t)(Base const&);

  setter_t mSetter;
  getter_t mGetter;

  BaseVTable(setter_t s, getter_t g): mSetter(s), mGetter(g) {}
} gBaseVTable(&Base_set, &Base_get);

Теперь я могу сделать что-то вроде:

void func()
{
  Base b;
  (*gBaseVTable.mSetter)(b, 3);
  std::cout << (*gBaseVTable.mGetter)(b) << std::endl; // print 3
}

Теперь перейдем к наследству. Давайте создадим еще одну структуру

struct Derived: Base {}; // yeah, Base does not have a virtual destructor... shh

void Derived_set(Derived& d, int i) { d.a = i+1; }

struct DerivedBaseVTable
{
  typedef void (*setter_t)(Derived&,int);
  typedef BaseVTable::getter_t getter_t;

  setter_t mSetter;
  getter_t mGetter;

  DerivedBaseVTable(setter_t s, getter_t g): mSetter(s), mGetter(g) {}
} gDerivedBaseVTable(&Derived_set, &Base_get);

И использование:

void func()
{
  Derived d;
  (*gDerivedBaseVTable.mSetter)(d, 3);
  std::cout << (*gDerivedBaseVTable.mGetter)(d) << std::endl; // print 4
}

Но как это автоматизировать?

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

Теперь, что происходит в случае множественного наследования? Ну, наследование очень похоже на композицию в плане размещения памяти:

|                                     Derived                                   |
|                 BaseA                 |                 BaseB                 |
| vpointer | field1 | field2 | padding? | vpointer | field1 | field2 | padding? |

Таким образом, для MostDerived будет две виртуальные таблицы: одна для изменения методов с BaseA и одна для изменения методов с BaseB.

Чистые виртуальные функции обычно представлены как нулевой указатель (просто) в соответствующем поле.

И, наконец, строительство и разрушение:

Строительство

  • BaseA создается: сначала инициализируется vpointer, затем атрибуты, затем тело конструктора
  • BaseB построено: vpointer, attribute, body
  • Derived построено: заменить vpointers (оба), атрибуты, тело

Уничтожение

  • Derived is destructed: тело деструктора, уничтожить атрибуты, вернуть базовые vpointers обратно
  • BaseB разрушено: тело, атрибуты
  • BaseA разрушено: тело, атрибуты

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

6 голосов
/ 04 июля 2010

Не могу, правда.В этом разделе делается попытка описать, что должно быть сделано в реализации C ++ с использованием таблиц виртуальных методов для обеспечения динамического связывания (в случае множественного наследования).

Если вы не используете компилятор, мой совет: Don 'беспокоитьсяПрочитайте свою любимую книгу C ++ по наследованию, виртуальным методам, множественному наследованию и виртуальному наследованию.

Плюс, использование vtable не требуется стандартом C ++ (IIRC), это детали реализации.Так что действительно, не беспокойся.

1 голос
/ 04 июля 2010

Как предположил mkluwe, vpointers на самом деле не являются частью языка. Однако, зная о Методы реализации могут быть полезны, особенно на низкоуровневом языке, таком как C ++.

Если вы действительно хотите изучить это, я бы порекомендовал Внутри объектной модели C ++ , которая объясняет это и многое другое подробно.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...