Виртуальная путаница наследования - PullRequest
5 голосов
/ 16 ноября 2011

Я читаю о наследовании, и у меня есть серьезная проблема, которую я не мог решить часами:

Учитывая класс Bar - это класс с virtual функциями,

class Bar
{
    virtual void Cook();
};

Чем отличается:

class Foo : public Bar
{
    virtual void Cook();
};

и

class Foo : public virtual Bar
{
    virtual void Cook();
};

?Часы поиска в Google и чтения принесли много информации о его использовании, но ни один из них на самом деле не говорит мне, в чем разница между ними, и просто смущает меня больше.

Ответы [ 4 ]

5 голосов
/ 16 ноября 2011

В функциональности нет большой разницы между двумя версиями. В случае наследования virtual каждая реализация обычно добавляет (vptr подобный) указатель (такой же, как в случае virtual функций). Что помогает избежать нескольких копий базового класса, сгенерированных из-за множественного наследования (проблема diamond наследство )

Кроме того, virtual наследование делегирует право вызывать конструктор своего базового класса . Например,

class Bar;
class Foo : public virtual Bar
class Other : public Foo  // <--- one more level child class

Итак, теперь Bar::Bar() будет вызываться непосредственно из Other::Other(), а также будет занимать первое место среди других базовых классов.

Эта функция делегирования помогает в реализации функциональности final class (на Java) в C ++ 03:

class Final {
  Final() {}
  friend class LastClass;
};

class LastClass : virtual Final {  // <--- 'LastClass' is not derivable
...
};

class Child : public LastClass { // <--- not possible to have object of 'Child'
};
4 голосов
/ 16 ноября 2011

Виртуальное наследование имеет значение только в том случае, если классы наследуются от Foo.Если я определю следующее:

class B {};
class L : virtual public B {};
class R : virtual public B {};
class D : public L, public R {};

, тогда конечный объект будет содержать только одну копию B, совместно используемую L и R.Без virtual объект типа D будет содержать две копии B, одну в L и одну в R.

Существует некоторый аргумент, что все наследование должно быть виртуальным (потому что в случаях, когда оно имеет значение, это то, что вы хотите большую часть времени).Однако на практике виртуальное наследование является дорогостоящим и в большинстве случаев необязательным: в хорошо спроектированной системе большая часть наследования будет просто иметь конкретный класс, наследующий от одного или нескольких «интерфейсов»;такой конкретный класс обычно не предназначен для того, чтобы быть производным от самого себя, поэтому проблем нет.Но есть важные исключения: если, например, вы определяете интерфейс, а затем расширения для интерфейса, расширения должны наследоваться практически от базового интерфейса, поскольку конкретная реализация может захотеть реализовать несколько расширений.Или, если вы разрабатываете миксины, в которых определенные классы реализуют только часть интерфейса, а последний класс наследует от нескольких из этих классов (по одному на часть интерфейса).В конце концов, критерий относительно того, наследовать ли виртуально или нет, не слишком сложен:

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

  • , если класс не предназначен для использования в качестве базового класса, виртуальное наследование не требуется, в противном случае

  • наследование должно быть виртуальным.

Есть несколько исключений, но вышеприведенные правила допускают ошибки на стороне безопасности;обычно «правильно» наследовать виртуально даже в тех случаях, когда виртуальное наследование не требуется.

Один последний момент: виртуальная база всегда должна инициализироваться самым производным классом, , а не класс, который напрямую наследует (и объявляет, что наследование является виртуальным).Однако на практике это не проблема.Если вы посмотрите на случаи, когда виртуальное наследование имеет смысл, это всегда случай наследования от интерфейса, который не будет содержать данных и, следовательно, будет иметь (только) конструктор по умолчанию.Если вы обнаружите, что наследуете практически от классов с конструкторами, которые принимают аргументы, пришло время задать несколько серьезных вопросов о дизайне.

3 голосов
/ 16 ноября 2011

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

struct A
{
  int a;
};

struct B : public virtual A
{
  int b;
}

struct C : public virtual A
{
  int c;
};

struct D : public B, public C
{
};

Существует одна копия переменной-члена a в случае D; Если бы A не было виртуальным базовым классом, в экземпляре D.

было бы два подобъекта A.
0 голосов
/ 16 ноября 2011

Виртуальная функция - это функция, которая, вероятно, будет иметь другую реализацию в производном классе (хотя это и не обязательно).

В вашем последнем примере - виртуальное наследование.Представьте себе случай, когда у вас есть два класса (A и B), производные от базового класса (назовем его «Base»).Теперь представьте третий класс C, производный от A и B. Без виртуального наследования C будет содержать две копии «Base».Это может привести к неоднозначности при компиляции.Важным моментом в виртуальном наследовании является то, что параметры для конструктора класса 'Base' (если таковые имеются) ДОЛЖНЫ быть предоставлены в классе C, потому что такие вызовы из A и B будут игнорироваться.

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