8. Реализация множественного наследования в C++ : преобразование типов (1) class B1 { int m_b1; int f1() {return m_b1;} }; class B2 { int m_b2; int f2() {return m_b2;} }; class D : B1, B2 { int m_d; int g() {return m_d+f1()+f2();} }; Что сгенерирует компилятор? int B1::f1(B1* this) {return this->m_b1;} int B2::f2(B2* this) {return this->m_b2;} int D::g(D* this) { return this->m_d + B1::f1( (B1*)this ) + B2::f2( (B2*)this ); } Подобъекты B1 и B2 лежат по разным адресам. Как компилятор реализует преобразования (B1*)this и ( B2*)this?
9. Реализация множественного наследования в C++ : преобразование типов (2) int D::delta_B1() {return 0;} int D::delta_B2() {return sizeof(B1);} int D::g(D* this) { return this->m_d + B1::f1(this+delta_B1()) + B2::f2(this+delta_B2()); } Вводятся псевдофункции D::delta_B1() и D::delta_b2() . NB: ниже вся арифметика указателей производится в байтах (не C ).
10. «Ромб наследования» Если буквально следовать описанной выше схеме, объект D будет содержать два подобъекта A. Как тогда реализовывать приведение (A*)pD ( например, для вызова методов A)?!
11. «Ромб наследования»: неоднозначность при обычном (невиртуальном) наследовании class A { int m_a; int f() {return m_a;} }; class B : A {}; class C : A {}; class D : B, C { int g() {return B::f()+C::f();} }; Что сгенерирует компилятор? int A::f(A* this) {return this->m_a;} int D::g(D* this) { return A::f(this + D::delta_B() + B::delta_A()) + A::f(this + D::delta_C() + C::delta_A()); } // В нашем случае обе delta_A равны 0 В контексте класса D любые имена из A должны явно квалифицироваться именем того или другого базового класса D (B или C). Без этого компилятор не сможет понять, какой из подобъектов A имеется в виду.
12. «Ромб наследования»: виртуальное наследование Бывают случаи, когда описанная неоднозначность классификации недопустима. Естественное решение: потребовать от компилятора, чтобы производные классы всегда содержали ровно один подобъект класса A. Виртуальное наследование class A {…}; class B : virtual A {…}; class C : virtual A {…}; class D : B, C {…}; Важно: виртуальнсть наследования описывается в контексте производных классов ( B, C) , а не в контексте базового класса ( A) !
13. Реализация виртуального наследования: проблема размещения подобъектов (1) class A { int m_a; int f_a() {return m_a;} }; class B : virtual A { int f_b() {return f_a();} }; class C : virtual A { int f_c() {return f_a();} }; class D : B, C { int f_d() {return f_a()+f_b()+f_c();} }; Что сгенерирует компилятор? int B::f_b(B* this) {return A::f_a(this+B::delta_A());} int C::f_c(C* this) {return A::f_a(this+C::delta_A());} int D::f_d(D* this) { return A::f_a(this+D::delta_A()) + B::f_b(this+D::delta_B()) + C::f_c(this+D::delta_C()); } Проблема: B::delta_A() и C::delta_A() должны быть разными в случае, если most derived object относится к классу D или классам B / C!
14.
15. Реализация виртуального наследования: проблема конструирования подобъектов (1) class A { int m_a; A() {m_a = 0;} }; class B : virtual A { B() {} }; class C : virtual A { C() {} }; class D : B, C { D() {} }; При конструировании объекта должны конструироваться подобъекты его баз? Что сгенерирует компилятор? Наивный вариант: A::A(A* this) {this->m_a = 0;} B::B(B* this) {A::A(this+B::delta_A());} C::C(C* this) {C::C(this+C::delta_A());} D::D(D* this) { B::B(this+D::delta_B()); C::C(this+D::delta_C()); } Проблема: в таком варианте конструктор для подобъекта A будет вызван два раза !
16.
17. Реализация виртуального наследования: проблема конструирования подобъектов (3) class A { int m_a; A() {m_a = 0;} }; class B : virtual A {B(){}}; class C : virtual A {C(){}}; class D : B, C {D(){}}; void test() { B b; C c; D d; } A::A(A* this) {this->m_a = 0;} B::B(B* this, bool bMostDerived) { if(bMostDerived) A::A(this+B::delta_A()); } // То же для C… D::D(D* this, bool bMostDerived) { if(bMostDerived) A::A(this+D::delta_A()); B::B(this+D::delta_B(), false); C::C(this+D::delta_C(), false); } void test() { B b; B::B(&b, true); // То же для C… D d; D::D(&d, true); }
18.
19.
20.
21. Специальный трюк: закрытое виртуальное наследование Задача: мы хотим, чтобы базовый класс мог бы «настраиваться» на каждый свой производный класс с помощью некоторых данных, специфичных для этого производного класса. // Файл Gadget.h class GadgetData {…}; class GadgetBase { protected: GadgetData m_GadgetData; GadgetBase(const GadgetData& x) {m_GadgetData = x;} }; class Gadget : private virtual GadgetBase {…}; // Файл FunnyGadget.h #include “Gadget.h” class FunnyGadget : public Gadget { static GadgetData s_FunnyData; public: SpecialGadget() : GadgetBase(s_FunnyData) {} };
Notas do Editor
Public vs. protected interface - two types of clients {part. Meyers1.41}. Containment as "has-a" or "is-implemented-via" relationship {Meyers1.40} Private inheritance - reasons of use instead of containment {Meyers1.42}