Граблі 2 Віртуальне успадкування
Стаття про те, як множинне спадкування все ускладнює. Як віртуальне спадкування, здавалося б, реалізовано нелогічно. Як на другий погляд логіка з'являється, але рівень складності та заплутаності продовжує зростати. Загалом, що складніше завдання, то простіші потрібно підбирати інструменти.
Все ґрунтується на реальних подіях, але приклади були максимально спрощені, щоб у них залишилася лише суть проблеми. Отже, в розроблюваному додатку використовувалася велика кількість однаково оброблюваних сутностей. Причому, одні були відображуваними, інші потрібно постійно оновлювати, треті з'єднували у собі те й інше. Відповідно з'явилося бажання реалізувати три базові класи
- Renderable: містить ознаку видимості та метод малювання
- Updatable: містить ознаку активності та метод оновлення стану
- VisualActivity= Renderable + Updatable
- JustVisible: просто видимий об'єкт
- JustVisiblePlusVisualActivity: JustVisible з оновлюваним станом

Відразу видно проблему — кінцевий клас успадковує Renderable двічі: як батько JustVisible і VisualActivity. Це не дає нормально працювати зі списками об'єктів, що відображаються
Виходить неоднозначність (ambiguous conversions) — компілятор не може зрозуміти, про успадковане за якою гілкоюRenderableйдеться. Йому можна допомогти, уточнивши напрямок шляхом явного приведення типу до одного з проміжних.
Компіляція пройде успішно, тільки помилка залишиться. У нашому випадку був потрібний один і той жеRenderable незалежно від того, яким чином він був успадкований. Справа в тому, що у випадку звичайного спадкування в класі-нащадку (JustVisiblePlusVisualActivity) міститься окремий екземпляр батьківського класу для кожної гілки.

Причому властивості кожного їх можна змінювати незалежно. Висловлюючись на c++, істинно вираз
Так що звичайне множинне наслідування для завдання не підходило. А ось віртуальне виглядало тією самою срібною кулею, яка була потрібна… Все, що потрібно — успадкувати базові класиRenderableтаUpdatableвіртуально, а решта — звичайним чином:
Усі успадковані віртуально класи представлені в нащадку лише один раз. І все б працювало, якби базові класи не мали конструкторів із параметрами. Але такі конструктори існували, і стався сюрприз. Кожен віртуально успадкований клас мав як стандартний конструктор так і параметризований
Класи-нащадки містили лише конструктори з параметрами
І все одно при створенні об'єкта
викликався конструкторRenderableза замовчуванням! На перший погляд це здавалося чимось диким. Але розглянемо докладніше, звідки взялося припущення, що наведений код повинен призводити до виклику конструктораRenderable::Renderable(bool visible)замістьRenderable::Renderable().

Породило проблему припущення, щоRenderableчудово розділиться міжJustVisible,VisualActivityіJustVisiblePlusUpdate. Але «диву» не судилося статися. Адже тоді можна було б написати щось на кшталт
повідомивши компілятору суперечливу інформацію, коли одночасно потрібно конструюванняRenderableз параметрамиtrueіfalse. Відкриватиможливість для таких парадоксів ніхто не захотів, відповідно і механізм працює іншим чином. КласRenderableу нашому випадку більше не є частиною ніJustVisible, ніVisualActivity, а належить безпосередньоJustVisiblePlusUpdate.

Це пояснює, чому викликався конструктор за умовчанням — конструктори віртуальних класів мають викликатися кінцевими спадкоємцями, тобто. робочим варіантом було б щось типу
При віртуальному наслідуванні доводиться, крім конструкторів безпосередніх батьків, явно викликати конструктори всіх віртуально успадкованих класів. Це не дуже очевидно і легко може бути втрачено в нетривіальному проекті. Отже, зайвий раз підтвердилася істина:не більше одного відкритого успадкування для кожного класу. Воно того не варте. У нашому випадку було прийнято рішення відмовитися від поділу наRenderableтаUpdatable, обмежившись одним базовимVisualActivity. Це додало деяку надмірність, але різко спростило загальну архітектуру — відстежувати та підтримувати всі віртуальні та звичайні випадки успадкування було надто затратно.
А у нас тут можна отримати грант на тестовий період Яндекс.Хмари. Варто лише у полі «секретний пароль» запровадити «Хабр»