C++ : наследование, часть 3 Дмитрий Штилерман, Рексофт
Краткое содержание <ul><li>Применение множественного наследования в моделировании. </li></ul><ul><li>Реализация множествен...
Начало: отвратительный пример <ul><li>Источник примера - книжка Страуструпа (к сожалению). </li></ul><ul><li>В чем здесь н...
Множественное наследование: что есть концептуально нового? <ul><li>Да почти ничего. </li></ul><ul><li>Отношение  “has-a” -...
Множественные классификации <ul><li>Класс находится в нескольких отношениях “is-a” , т.е. является предметом нескольких (о...
Множественное положение в одной классификации - «кровосмешение» <ul><li>Объект принадлежит нескольким классам, не являющим...
Реализация множественного наследования в C++ : подобъекты class B1  {int m_b1;}; class B2  {int m_b2;}; class D : B1, B2  ...
Реализация множественного наследования в C++ : преобразование типов (1) class B1  {   int m_b1;   int f1()   {return m_b1;...
Реализация множественного наследования в C++ : преобразование типов (2) int D::delta_B1()  {return 0;} int D::delta_B2()  ...
«Ромб наследования» Если буквально следовать описанной выше схеме, объект  D  будет содержать два подобъекта  A. Как тогда...
«Ромб наследования»: неоднозначность при обычном (невиртуальном) наследовании class A  {   int m_a;   int f() {return m_a;...
«Ромб наследования»: виртуальное наследование Бывают случаи, когда описанная неоднозначность классификации недопустима. Ес...
Реализация виртуального наследования: проблема  размещения подобъектов  (1) class A  {   int m_a;   int f_a() {return m_a;...
Реализация виртуального наследования: проблема  размещения подобъектов  (2) <ul><li>В зависимости от того, к какому классу...
Реализация виртуального наследования: проблема  конструирования подобъектов  (1) class A  {   int m_a;   A() {m_a = 0;}  }...
Реализация виртуального наследования: проблема  конструирования подобъектов  (2) <ul><li>Решение проблемы конструирования:...
Реализация виртуального наследования: проблема  конструирования подобъектов  (3) class A  {   int m_a;   A() {m_a = 0;}  }...
Реализация множественного и виртуального наследования:  другие проблемы <ul><li>Виртуальные базы без конструкторов по умол...
Виртуальное наследование: основной концептуальный конфликт <ul><li>Требование однозначности классификации является частью ...
Виртуальное наследование: основное правило применения <ul><li>Виртуальный базовый класс и его непосредственные потомки  до...
Специальный трюк: закрытое виртуальное наследование  Задача: мы хотим, чтобы базовый класс   мог бы «настраиваться» на каж...
Upcoming SlideShare
Loading in...5
×

OO Design with C++: 3. Inheritance, part 3

584

Published on

Published in: Technology
0 Comments
1 Like
Statistics
Notes
  • Be the first to comment

No Downloads
Views
Total Views
584
On Slideshare
0
From Embeds
0
Number of Embeds
1
Actions
Shares
0
Downloads
0
Comments
0
Likes
1
Embeds 0
No embeds

No notes for slide
  • Public vs. protected interface - two types of clients {part. Meyers1.41}. Containment as &amp;quot;has-a&amp;quot; or &amp;quot;is-implemented-via&amp;quot; relationship {Meyers1.40} Private inheritance - reasons of use instead of containment {Meyers1.42}
  • OO Design with C++: 3. Inheritance, part 3

    1. 1. C++ : наследование, часть 3 Дмитрий Штилерман, Рексофт
    2. 2. Краткое содержание <ul><li>Применение множественного наследования в моделировании. </li></ul><ul><li>Реализация множественного наследования в C++ . </li></ul><ul><li>Виртуальное наследование и его р еализация в C++ . </li></ul><ul><li>Основное правило применения виртуального наследования. </li></ul><ul><li>Трюк с закрытым виртуальным наследованием. </li></ul>
    3. 3. Начало: отвратительный пример <ul><li>Источник примера - книжка Страуструпа (к сожалению). </li></ul><ul><li>В чем здесь неправда? Предлог “with” подразумевает отношение “has-a” и использование композиции, либо изоляции. </li></ul>
    4. 4. Множественное наследование: что есть концептуально нового? <ul><li>Да почти ничего. </li></ul><ul><li>Отношение “has-a” - никогда не моделируется через наследование . </li></ul><ul><li>Отношение “is-implemented-by” - иногда может моделироваться через private наследование. При этом базовый класс целиком является деталью реализации и не интересен клиентам производных классов  можем пользоваться этим приемом столько раз, сколько хотим. </li></ul><ul><li>Отношение “is-a” - есть два случая: </li></ul><ul><ul><li>Объект входит в несколько различных классификаций. </li></ul></ul><ul><ul><li>Объект входит несколько раз в одну и ту же классификацию. </li></ul></ul>
    5. 5. Множественные классификации <ul><li>Класс находится в нескольких отношениях “is-a” , т.е. является предметом нескольких (обычно независимых) классификаций . </li></ul><ul><ul><li>Классификации независимы, если положение объекта в одной из них не оказывает влияния на положение в другой. </li></ul></ul><ul><li>Важно (как и в биологии), что базовые классы “не являются родственниками”, т.е. не имеют общих предков. </li></ul><ul><li>Обычно разным клиентам класса будут интересны разные его типы (или интерфейсы, им реализуемые). </li></ul>
    6. 6. Множественное положение в одной классификации - «кровосмешение» <ul><li>Объект принадлежит нескольким классам, не являющимся подклассами один другого, но имеющим общий суперкласс. </li></ul><ul><li>Неиерархическая классификация! Вместо дерева (или леса) имеем ациклический орграф ( DAG ). </li></ul><ul><li>На практике такая ситуация может встретиться, когда нам нужно реализовать несколько интерфейсов (в нашем примере - чтобы уметь использовать iostream как ios, istream и ostream). Разные клиенты опять же будут видеть разные интерфейсы нашего класса. </li></ul><ul><li>Если нам нужно только наследовать реализаци и (ради их повторного использования), то мы имеем отношение “is -implemented-by”. </li></ul>
    7. 7. Реализация множественного наследования в C++ : подобъекты class B1 {int m_b1;}; class B2 {int m_b2;}; class D : B1, B2 {int m_d;}; <ul><li>Объект производного класса содержит подобъекты для каждого базового класса. </li></ul><ul><li>Объект, не являющийся чьим-то подобъектом называется most derived object . </li></ul>
    8. 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. 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. 10. «Ромб наследования» Если буквально следовать описанной выше схеме, объект D будет содержать два подобъекта A. Как тогда реализовывать приведение (A*)pD ( например, для вызова методов A)?!
    11. 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. 12. «Ромб наследования»: виртуальное наследование Бывают случаи, когда описанная неоднозначность классификации недопустима. Естественное решение: потребовать от компилятора, чтобы производные классы всегда содержали ровно один подобъект класса A. Виртуальное наследование class A {…}; class B : virtual A {…}; class C : virtual A {…}; class D : B, C {…}; Важно: виртуальнсть наследования описывается в контексте производных классов ( B, C) , а не в контексте базового класса ( A) !
    13. 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. 14. Реализация виртуального наследования: проблема размещения подобъектов (2) <ul><li>В зависимости от того, к какому классу относится most derived object , положение подобъекта виртуальной базы будет изменяться. </li></ul><ul><li>Решение - сделать функции delta виртуальными и переопределять их в производных классах. </li></ul><ul><li>« Виртуальность » размещения подобъекта базового класса - одно из объяснений термина «виртуальное наследование» . </li></ul>
    15. 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. 16. Реализация виртуального наследования: проблема конструирования подобъектов (2) <ul><li>Решение проблемы конструирования: </li></ul><ul><ul><li>Конструкторы виртуальных баз должны вызываться из конструктора класса, к которому принадлежит most derived object . </li></ul></ul><ul><li>Ограничения на реализацию, являющиеся следствиями из других общих правил C++ : </li></ul><ul><ul><li>Деструкторы должны вызываться в порядке, обратном порядку вызова конструкторов. </li></ul></ul><ul><ul><li>Механизмы, связанные с виртуальным наследованием, не должны встраиваться в базовый класс (т.к. он не знает, виртуальная ли он база  чем не пользуемся, за то не платим). </li></ul></ul>
    17. 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. 18. Реализация множественного и виртуального наследования: другие проблемы <ul><li>Виртуальные базы без конструкторов по умолчанию (конструктор должен явно вызываться в каждом производном классе). </li></ul><ul><li>Приведение типов вниз (или вбок) по иерархии наследования (когда функция delta вычитается ). </li></ul><ul><li>Построение VMT и вызов виртуальных функций. </li></ul><ul><li>Реализация RTTI . </li></ul><ul><li>Реализация обработки исключений. </li></ul><ul><li>M.Ellis & B.Stroustrup, “The Annotated C++ Reference Manual” </li></ul><ul><li>B.Stroustrup, “The Design and Evolution of C++” </li></ul>
    19. 19. Виртуальное наследование: основной концептуальный конфликт <ul><li>Требование однозначности классификации является частью анализа и концептуально относится к базовому классу. Поэтому каждый базовый класс должен наследоваться или всегда виртуально, или всегда невиртуально. </li></ul><ul><li>Ви ртуальность наследования является частью дизайна и определяется производными классами. Поэтому сформулированное ограничение не может поддерживаться языком напрямую. </li></ul>
    20. 20. Виртуальное наследование: основное правило применения <ul><li>Виртуальный базовый класс и его непосредственные потомки должны разрабатываться одновременно и входить в одну единицу дизайна (модуль). </li></ul><ul><li>Наследование извне этого единственного модуля должно быть запрещено (например, через protected конструктор ). </li></ul><ul><li>Если мы проектируем базовый класс, от которого может наследовать кто угодно, наследование от него не должно быть виртуальным. </li></ul>
    21. 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) {} };

    ×