|
Программирование >> Решение нетривиальных задач
8 На самом деле правильнее сказать, что во время компиляции компилятор не знает, от какого из базовых классов parent объект child наследует обработчик сообщения go to sleep(), хотя эта правильность и может сбить с толку. Вы можете спросить, почему неопределенность имеет значение, ведь эта функция одна и та же в обоих классах. Компилятор не может создать ветвление времени выполнения, так как не знает, какое значение присвоить указателю this, когда он вызывает функцию-член базового класса. Затем, у child есть mother и father, у каждого из которых есть parent. Проблема с philip.go to sleep() состоит в том, что компилятор не знает, какой из объектов parent должен получить это сообщение: тот, который в mother, или тот, который в father.8 Одним из путей решения этой проблемы является введение уточняющей функции, которая направляет сообщение нужному классу (или обоим): class parent { public: go to sleep(); }; class mother : public parent {}; class father : public parent {}; class child : public mother, public father public: go to sleep() { mother::go to sleep(); father::go to sleep(); Другим решением является виртуальный базовый класс: class parent {}; class mother : virtual public parent {}; class father : virtual public parent {}; class child : public mother, public father {} который заставляет компилятор помещать в объект child лишь один объект parent, совместно используемый объектами mother и father. Двусмысленность исчезает, но появляются другие проблемы. Во-первых, нет возможности показать на уровне потомка, хотите вы или нет виртуальный базовый класс. Например, в следующем коде tree list node может быть членом как дерева, так и списка одновременно: class node; class list node : public node {}; class tree node : public node {}; class tree list node : public list node, public tree node {}; В следующем варианте tree list node может быть членом или дерева, или списка, но не обоих одновременно: class node; class list node : virtual public node {}; class tree node : virtual public node {}; class tree list node : public list node, public tree node {}; Вам бы хотелось делать этот выбор при создании tree list node, но такой возможности нет. Второй проблемой является инициализация. Конструкторы в list node и tree node, вероятно, инициализируют базовый класс node, но разными значениями. Если имеется всего один node, то какой из конструкторов выполнит эту инициализацию? Ответ неприятный. Инициализировать node должен наследуемый последним производный класс (tree list node). Хотя это действительно плохая мысль - требовать, чтобы класс знал о чем-либо в иерархии, кроме своих непосредственных родителей - иначе было бы слишком сильное внутреннее связывание. Обратная сторона той же самой проблемы проявляется, если у вас есть виртуальные функции как в следующем коде: class persistent public: virtual flush() = 0; class doc1: virtual public persistent public: virtual flush() { /* сохранить данные doc1 на диске */ } class doc2: virtual public persistent public: virtual flush() { /* сохранить данные doc2 на диске */ } class superdoc : public doc1, public doc2 {}; persistent *p = new superdoc(); p->flush(); ОШИБКА: какая из функций flush() вызвана? 102. Смешения не должны наследоваться от чего попало 103. Смешения должны быть виртуальными базовыми классами 104. Инициализируйте виртуальные базовые классы при помощи конструктора, используемого по умолчанию Вы можете свести до минимума рассмотренные ранее проблемы, стараясь придерживаться следующих правил (многие смешения не могут соответствовать им всем, но вы делайте все от вас зависящее): Если можно, то смешения не должны наследоваться от чего попало, тем самым полностью устраняя проблему ромбовидной иерархии при множественном наследовании. Для смешения должна обеспечиваться возможность быть виртуальным базовым классом для того, чтобы не возникала проблема неопределенности в случае, если у вас все же получилась ромбовидная структура классов. Если можно, то смешение должно всегда строиться с использованием только конструктора по умолчанию (не имеющего аргументов). Это упрощает оформление смешения в качестве виртуального базового класса, потому что вам не нужно будет заботиться об инициализации большей части наследуемого объекта. В конце концов, по умолчанию всегда используется конструктор по умолчанию.
|
© 2006 - 2024 pmbk.ru. Генерация страницы: 0
При копировании материалов приветствуются ссылки. |