От порядка родителей будет зависеть то, как лежат данные.
struct D: A,B,C { };
От порядка родителей зависит порядок вызова конструкторов и деструкторов.
Конструкторы: A->B->C->D
Деструкторы: D->C->B->A
В основном множественное наследование используется при наследовании от интерфейсов. Интерфейс в C++ это класс, у которого все функции виртуальные. Наследование реализации применяется очень редко.
Мы видим что второй вариант с одиночным наследованием проще и логичнее.
struct Circle { Point center_; double radius_; }
Но некоторые делают:
struct Circle:Point { double radius_; }
Как мы видим, множественное наследование порой логичнее и проще заменить на включение объекта внутрь нашего класса.
Одним из аргументов против множественного наследования является то, что в большинстве промышленных языков его нет, и там без него обходятся без особых сложностей.
Наследование от нескольких интерфейсов является одним из самых оправданных случаев применения множественного наследования.
Если в базовых классах есть метод с одинаковым именем, и мы его переопределили в наследнике, то он переопределятся сразу для всех родителей.
struct A { virtual void foo()=0; }; struct B { virtual void foo() { std::cout << "B::foo()" << std::endl; } }; struct C:A,B { virtual void foo() { std::cout << "C::foo()" << std::endl; } }; int main() { C c; c.B::foo(); system("PAUSE"); return 0; }
Результат: B::foo();
Во время последовательного вызова конструкторов при создании объекта класса, внутри тела каждого конструктора указатель на таблицу виртуальных функций будет установлен на тот класс, для которого вызван конструтор. Так как контекст будет изменяться при вызове конструторов, вызывать в них виртуальные функции является плохим тоном.
Рассмотрим следующую иерархию
При обычно наследовании мы бы получили в классе C две копии класса A. Но если семантика нашего наследования предполагает что C является A, также как B1 и B2, то мы получаем логическое противоречие такой конструкции. Также такая конструкция будет не верна, если нам нужно иметь одну копию класса A в C. Для преодоления этой ситуации в C++ было введено виртуальное наследование. Ключевое слово virtual при наследовании показывает компилятору, что класс наследник может учавствовать в ромбовидных иерархиях.
Вот как будет выглядеть код этого примера с виртуальным наследованием:
struct A { void foo() { std::cout << "A" << std::endl; } }; struct B1: virtual A { }; struct B2: virtual A { }; struct C:B1,B2 { }; int main() { C c; c.foo(); system("PAUSE"); return 0; }
Компилятор добавляет в таблицу виртуальных функций класса наследника функцию, которая возвращает указатель на объект базового класса. Таким образом эти функции у B1 и B2 будут возвращать один и тот же указатель на объект A, сожержащийся в C. Реализация виртуального наследования не регламентируется, поэтому есть и другие подходы, например, основанный на отдельной таблице виртуальных классов.
В С++ существует ключевое слово friend, означающее что класс или функция будут видеть все данные и методы класса, с которым они дружат. Если A друг B, то это не значит что B друг A.
Пример (Функция foo Дружит с классом Bar):
struct Bar { friend void foo(Bar& bar); private: static int data_; void MakeCoffee() { } }; void foo(Bar& bar) { bar.MakeCoffee(); std::cout << Bar::data_ << std::endl; } int Bar::data_;