Ответ 1
В следующей статье от создателя С++ описывается возможная реализация множественного наследования:
Множественное наследование для С++ - Bjarne Stroustrup
Одиночное наследование легко реализовать. Например, в C наследование можно моделировать следующим образом:
struct Base { int a; }
struct Descendant { Base parent; int b; }
Но с множественным наследованием компилятор должен расположить несколько родителей внутри вновь созданного класса. Как это делается?
Проблема, которую я вижу, возникает в том случае, если родители должны быть организованы в AB или BA или, возможно, даже по-другому? И затем, если я сделаю бросок:
SecondBase * base = (SecondBase *) &object_with_base1_and_base2_parents;
Компилятор должен учитывать, следует ли изменять исходный указатель или нет. Подобные сложные вещи необходимы для виртуальных машин.
В следующей статье от создателя С++ описывается возможная реализация множественного наследования:
Множественное наследование для С++ - Bjarne Stroustrup
эта довольно старая статья MSDN о том, как она была реализована в VС++.
И затем, если я сделаю бросок:
SecondBase base = (SecondBase *) object_with_base1_and_base2_parents;
Компилятор должен учитывать, следует ли изменять исходный указатель или нет. Подобные сложные вещи с виртуальными.
С невирусным наследованием это менее сложно, чем вы могли подумать - в момент компиляции, компилятор знает точное расположение производного класса (в конце концов, компилятор сделал макет). Обычно все, что происходит, это фиксированное смещение (которое может быть равным нулю для одного из базовых классов) добавляется/вычитается из указателя производного класса.
С натуральным наследованием это, возможно, немного сложнее - это может включать захват смещения от vtbl (или аналогичного).
Книга Стэна Липпмана, "Внутри объектной модели С++" имеет очень хорошие описания того, как этот материал может (и часто на самом деле) работать.
Родители расположены в том порядке, в котором они указаны:
class Derived : A, B {} // A comes first, then B
class Derived : B, A {} // B comes first, then A
Ваш второй случай обрабатывается в зависимости от компилятора. Одним из распространенных методов является использование указателей, размер которых больше размера указателя платформы, для хранения дополнительных данных.
Это интересная проблема, которая на самом деле не является специфичной для С++. Все становится более сложным, когда у вас есть язык с несколькими отправками, а также множественное наследование (например, CLOS).
Люди уже отметили, что существуют различные способы решения этой проблемы. Возможно, вы немного читали о интересных в этом контексте метаобъектных протоколах (MOP)...
Он полностью подчиняется компилятору, как это делается, но я полагаю, что он вообще выполняется через иерархическую структуру vtables.
Я выполнил простой эксперимент:
class BaseA { int a; };
class BaseB { int b; };
class Descendant : public BaseA, BaseB {};
int main() {
Descendant d;
BaseB * b = (BaseB*) &d;
Descendant *d2 = (Descendant *) b;
printf("Descendant: %p, casted BaseB: %p, casted back Descendant: %p\n", &d, b, d2);
}
Выход:
Descendant: 0xbfc0e3e0, casted BaseB: 0xbfc0e3e4, casted back Descendant: 0xbfc0e3e0
Хорошо понимать, что статическое кастинг не всегда означает "изменить тип, не касаясь содержимого". (Ну, когда типы данных не подходят друг другу, тогда также будет помеха в контенте, но в другой ситуации IMO).