|
Программирование >> Полиморфизм без виртуальных функций в с++
типа виртуального базового класса к типу производного класса требует некоторого действия во время выполнения, основанного па той информации, которая хранится в объекте базового класса. Но в объектах простых классов такой информации не может быть при заданных ограничениях на размещение в памяти. Если бы я относился к приведениям типов с меньшей осторожностью, то счел бы невозможность приведения от типа виртуального базового класса более серьезным недостатком. Так или иначе, классы, доступ к которым производится через виртуальные функции, и классы, где просто хранится несколько элементов данных, чаще всего являются наилучшими кандидата,ми на роль виртуальных базовых классов. Если в базовом классе есть только члены-данные, не следует передавать указатель на него, как на полный класс. Если же, с другой стороны, в базовом классе есть виртуальные функции, то их можно вызывать. В любом случае в приведении типа не должно возникать необходимости. Ну, а если без приведения типа от базового к производному классу все-таки не обойтись, оператор dynamic cast (см. раздел 14.2.2) решает проблему для базовых классов с виртуальными функциями. Обыкновенные базовые классы занимают одну и ту же позицию во всех объектах данного производного класса, и эта позиция известна компилятору. Поэтому указатель на обыкновенный базовый класс можно приводить к типу указателя на производный класс без каких-либо дополнительных проблем или затрат. Если бы было введено требование о том, что всякий потенциальный виртуальный базовый класс должен объявляться явно, то ко всем им разрешалось бы применять специальные правила, например добавить информацию, необходимую для приведения типа от виртуального базового класса к производному от него. Если бы я решил сделать виртуальный базовый класс специальным видом класса, то программистам пришлось бы держать в голове две концепции: обыкновенного и виртуального классов, что неудобно. Вместо этого можно было пойти на дополнительные затраты, добавив информацию, необходимую только настоящему виртуальному базовому классу, во все объекты классов. Но такой прием привел бы к заметным издержкам даже в тех приложениях, где виртуальные базовые классы не используются, а также возникли бы проблемы совместимости, относящиеся к модели размещения в памяти. Поэто.му я разрешил использовать любой класс в качестве виртуального базового и согласился с запретом на приведение типов, сочтя его ограничением, присущим использованию виртуальных базовых классов. 12.5. Комбинирование методов Часто функция в производном классе использует функцию с тем же именем, определенную в одном из базовых классов. Этот прием называется комбинированием методов и поддерживается в некоторых объектно-ориентированных языках, но не в С++ (за исключением конструкторов, деструкторов и операций копирования). Может, стоило реанимировать понятие о функциях call и return (см. раздел 2.11.3) в подражание методам : before и : after, имеющимся в языке CLOS, но пользователи и так жалуются на сложности механизмов .множественного наследования. Вместе с тем методы легко комбинировать вручную. Главное - избежать нескольких вызовов функции из виртуального класса, если это нежелательно. Вот один из возможных подходов: class W { . . . protected: void f() { /* выполняет то, что необходимо самому W */ } ... public: void f() { f(); } . . . В каждом классе имеется защищенная функция f (), выполняющая операции, необходимые самому классу, и используемая производными классами. Есть также открытая функция f (), которая является интерфейсом, предлагаемым сторонним классам, функциям и т.д. В производном классе f () вызывает собственную f () и позволяет базовы.м классам вызвать их f (): class А : public virtual W { ... protected: void f() { /* выполняет то, что необходимо А */ } ... public: void fО { fО; W:: fО; } . . . class В : public virtual W { . . . protected: void f() { /* выполняет то, что необходимо В */ } . . . public: void f О { f О ; W:: f О ; } . . . В частности, такой стиль позволяет в классе, который косвенно произведен от класса W дважды, вызывать W: : f () только один раз: class С : public А, public В, public virtual W { . . . protected: void f() { /* выполняет то, что необходимо С */ } . . . public: void f() { f(); A:: f(); B:: f(); W:: f(); } . . . Это менее удобно, чем автоматически генерируемые составные функции, но в некоторых отношениях обладает большей гибкостью. 12.6. Полемика о множественном наследовании Множественное наследование в С++ вызвало споры ([Cargill, 1991], [Carroll, 1991], [Waldo, 1991], [Sakkinen, 1992], [Waldo, 1993]) no нескольким причинам. Аргументы против него в основном касались реальной и воображаемой сложности концепции, ее полезности и влияния, которое она оказывает на другие средства языка и инструментальные средства: □ множественное наследование виделось как первое существенное расширение С++. Некоторые опытные пользователи С++ считали, что это ненужное усложнение, которое повлечет за собой поток новых средств. Например, на самой первой конференции по С++ в Санта-Фе (см. раздел 7.1.2) Том Каргилл сорвал аплодисменты, высказав забавное, но не очень реалистичное предложение: каждый, кто предлагает включить в С++ новое средство, должен сказать, какое из старых средств, равных по сложности новому, следует исключить. Мне такой подход нравится, но я все же не могу утверждать, что С++ стал бы лучше, не будь в нем множественного наследования, или что С++ образца 1985 г. лучше, чем С++ 1993 г. Веселье продолжил Джим Уолдо Waldo). Он продолжил мысль Тома, высказав следующую идею: обязать предлагающих новые средства пожертвовать... свою почку. Это, по мнению Джима, заставит каждого не один раз подумать, прежде чем вносить предложение, причем даже лишенные инстинкта самосохранения не смогут внести более двух предложений. Впрочем, не все были так настроены против новых возможностей, как можно было бы подумать, читая журналы, сетевые новости и выслушивая вопросы, задаваемые после доклада; □ я реализовал множественное наследование так, что издержки неизбежны даже для тех, кто пользуется лишь одиночным. Это нарушает правило чем не пользуетесь, за то не платите (см. раздел 4.5) и создает ложное впечатление, что множественное наследование якобы неэффективно вовсе. Такие издержки казались мне вполне приемлемыми, потому что они совсем невелики (один доступ к массиву плюс одно сложение на каждый вызов виртуальной функции) и потому что я знал простой способ реализации множественного наследования без всяких изменений реализации вызовов виртуальных функций в иерархии одиночного наследования (см. раздел 12.4). Я выбрал такую субоптимальную реализацию, поскольку она в наибольшей степени переносима; □ Smalltalk не поддерживает множественное наследование, а для многих пользователей слова объектно-ориентированное программирование и Small-talk - синонимы. Такие люди часто подозревают, что если Smalltalk не поддерживает какую-то возможность, то она либо плоха, либо не нужна. Разумеется, я не разделяю этих взглядов. Очевидно, Smalltalk и выиграл бы от наличия множественного наследования, возможно, - нет; это не имеет отношения к делу. Однако мне было ясно, что некоторые приемы, которые
|
© 2006 - 2024 pmbk.ru. Генерация страницы: 0
При копировании материалов приветствуются ссылки. |