Б. Страуструп - Дизайн и Эволюция C++. 2006 (1160775), страница 70
Текст из файла (страница 70)
Сейчас, как и тогда, я думаю, что у всех вышеупомянутых доводов есть один общий недостаток — множественное наследование в них трактуется чересчур серьезно. А ведь это средство не решает — да и не должно решать — всех проблем, поскольку обходится слишком дешево. Иметь в своем распоряжении множественное наследование иногда удобно. Грейди Буч [ВоосЬ, 1991) выразил эту мысль чуть более экспрессивно: «Множественное наследование — как парашют; необязательно пользоваться им часто.
Зато, когда возникает необходимость, без него не обойтись». Такое мнение частично основано на опыте, приобретенном при переписывании компонент Буча с Аг[а на С++ (см. раздел 8.4.1). Библиотека контейнерных классов и ассоциированных с ними операций, которую реализовали Грейди Буч и Майк Вило, — это один из лучших примеров применения множественного наследования [ВоосЬ, 1990), [ВоосЬ, 1993Ь). Я прекратил участие в дебатах по поводу множественного наследования: оно есть в С++ и исключить или радикально изменить его невозможно.
Как уже было сказано, считаю, что множественное наследование временами полезно. Некоторые настаивают на том, что для их подходов к проектированию и реализации данное средство абсолютно необходимо. Однако в широком смысле о пользе множественного наследования в С++ судить рано — не хватает данных и опыта. Наконец, я не люблю тратить время на бесплодные дискуссии. По всей вероятности, наиболее удачно множественное наследование применяется в следующих ситуациях: о объединение независимых или почти независимых иерархий; примером могут служить классы саэ1« и Жэр1ауес1 (см.
раздел 12.2); о композиция интерфейсов; примером является библиотека потокового ввода/вывода (см. раздел 12.3); о составление класса из интерфейса и реализации; пример — класс э11эс зес (см. раздел 13.2.2). Дальнейшие примеры множественного наследования можно найти в разделах 13.2.2, 14.2.7 и 16А. В основном неудачи связаны с попытками насильно внедрить стиль, чуждый С++. В частности, прямое копирование применяемого в СЬОТА стиля проектирования, который основан на линеаризации разрешения неоднозначности, сопоставлении имен, разделяемых в иерархии, и применении методов: Ье1оге и: алеет для создания составных операций, значительно усложняет большие программы, 1ИИИИИИИ Делегирование 12.7. Делегирование В первоначальном проекте множественного наследования, который был представлен на Европейской конференции группы пользователей 1ЛЧ1Х, состоявшейся в Хельсинки в мае 1987 г.
181гонвггнр, 19871, фигурировало понятно делегирования [А8Ьа, 19861. Пользователю разрешалось задать указатель на некоторый класс вместе с именами базовых классов в объявлении класса. Описанный таким образом объект использовался точно так же, как если бы это был объект, представляющий базовый класс. Например: с)аза в ( Ьос )и той Г(); ); с)ааа с : *р ( В" р; гос с; ); Нотация : *р означает, что объект, на который указывает р, будет использоваться так, будто он представляет базовый класс для С. чогд Г(с* с) ( с->г(); гг означает с->р->г() После инициализации с:: р объект класса с выглядел бы примерно так, как это показано на рис.
12.10. в* р ос с Рис. 12. 10 Ясно, что лве эти проблемы взаимосвязаны. Разумеется, пользователи были предупреждены. Прелостережения не помогли. Более того, я сам забыл собственные Концепция выглядела многообещаюшей для представления структур, требующих большей гибкости, чем может дать обычное наследование. В частности, присваивание делегирующему указателю могло бы использоваться для изменения конфигурации объекта во время выполнения. Реализация была тривиальной, затраты — минимальными. Поэтому данную идею испытали несколько пользователей. Много времени и сил здесь положил Билл Хопкинс (В(П НорЫпз). К сожалению, все пользователи, применившие механизм делегирования, пострадали от серьезных ошибок и путаницы.
Из-за этого возможность была исключена как из проекта, так и из С1гопс версии 2.0. Причины ошибок: а функции в делегирующем классе не замещают функции в классе, которому операция делегируется; е функция, которой передается управление, не может воспользоваться функциями из делег ирующего класса или каким-то иным способом «вернуться» в делегирующий объект. ИИИИИИИИ!1 Множественное наследование 12.8.
Переименование В конце 1989 — начале 1990 гг. была обнаружена проблема, проистекающая из конфликта имен в иерархиях с множественным наследованием [АКМ): «Объединение двух иерархий классов путем использования их в качестве базовых классов дпя общего производного может стать источником трудностей, когда одно и то же имя используется в обеих иерархиях, но обозначает разные операции. Например: с1авв Ьогсегу ( // ч1гсца1 Ьпс г)ган() г ): лотерея // тянуть жребий с1авв огарЫса10Ь)ест ( // тгтгсца1 чотс) г)ган()г )т // графический объект // рисовать с1аев Ьоггегуз1пщ1аг1оп // моделирование лотереи рцЫЬс ьогсегу, рцЫ1с огарИса10Ь)есс ( // )г В классе Ьоггегуяалщ1агьоп нам хотелос~ бы заместить как ьоссегу г г г)ган (), так иогарЫса10Ь)есЫ:г)ган(), нодвумя разными функциями, посколькузкзеглпляры г)гам() в базовых классах имеют разную семантику. желательно, чтобы в классе ьог с егуз1вщ1а с Ьоп были различные, не допускающие двусмысленного толкования имена дпя унаследованных функций ьогсегуг гг)гам() и огарыса10ъ)есс г г г)ган() .
Семантика концепции очень проста, а реализация тривиальна; трудно лишь найти подходящий синтаксис. Предлагался токой вариант: с1авв Ьоггегуз1пгц1аг1оп рцЫ1с ьоггегу, рцЫ1с огарЫса10Ь)есс ( правила и попался в ловушку. Таким образом, проблему нельзя было назвать мелким огрехом, который исправляется с помощью обучения и предупреждений компилятора.
В то время она казалась непреодолимой. Сегодня мне кажется, что указанные проблемы имеют фунламентальный характер. Для решения первой потребовалось бы изменять таблицу виртуальных функций объекта, которому делегируется управление, если он связан с делегируюшим обьектом. Это плохо согласуется с языком в целом и с большим трудом поддается разумному определению. Кроме того, обнаружились примеры, когда мы хотели, чтобы два разных объекта делегировали управление одному и тому же «разделяемому» объекту. Нашлись и такие задачи, в которых нужно было делегировать управление через в* объекту производного класса Ьг.
Поскольку делегирование не поддержано в С++ напрямую, нужно искать обходные пути, если оно все же необходимо. Часто решить проблему, требующую делегирования, можно с помощью «умного» указателя (см. раздел 11.5.1). Вместо этого делегируюп(ий класс может предоставить полный интерфейс, а затем запросы «вручную» переадресовываются какому-то другому объекту (см. раздал 11.5.2). 1! ИИИИИИИ Переименование // чтггца1 гпг 1 оган() = Ьоггегуггс)ганг ч1ггца1 1пс до г)гаи() = сгарп1са10)з)есс:гг)ганг ); Это естественное расширение синтаксиса виртуальных функцийм «Переименования можно достичь, введя дополнительный класс для каждого класса, содержащего виртуальную функцию, которая нуждается в замещении функцией с другим именем.
Также дпя каждой такой функции необходимо написать специальную переодресующую функцию. Например: с1авв ььоггегу : рцЫтс ьоггегу ( чфггца1 Ьог 1 г)гаи() = Ог 1аг г)ган() ( гесагп 1 бган()т ) // замещает )т с1авз ССгарЫса10Ь)есс: рцЫЬс Стар)т1са10Ь1есс ( нтггца1 уо1г) до дгаы() = От тго1г) с)ган() ( до г)гаи()г ) // замещает )т с1авв Ьоггегуэтщц1асьоп рцЫ1с ЬЬогсегу, рцЫ1с ССгар)т1са10Ьтесг ( // 1пг 1 с)ган()г // замещает 1пг до бган()г // замещает ); Следовательно, расширение языка дпя выражения переименования не является необходимым и рассматривать его стоит лишь в случае, если окажется, что потребность в разрешении таких конфликтов имен возникает частот.
На следующем заседании я описал прием. Во время обсуждения мы согласились, что вряд ли конфликты встречаются настолько часто, чтобы вводить в язык После продолжительной полемики в рабочей группе по расширениям и подготовки необходимых документов Мартином О'Риорданом и мной, предложение было вынесено на заседание комитета по стандартизации в Сиэтле в июле (990 г. Подавляющее большинство участников обсуждения высказалось за то, чтобы сделать это первым ястороннимь расширением С++.
Но в этот момент Бет Крокетт (Вег(т Сгос)гегг) из компании Арр!е приостановила работу комитета, воспользовавшись яправилом двух недельж любой представитель комитета может перенести голосование по предложению на следующее заседание, если тема обсуждения не была передана ему, по крайней мере, за две недели до начала текущего заседания. Это правило защищает от поспешного принятия решения по вопросу, в котором человек разобрался не до конца, и гарантирует достаточное время для консультаций с коллегами. Вы, наверное, догадались, что этим вето Бет не снискала популярности. Однако ее осторожность оказалась не напрасной и уберегла нас от серьезной ошибки.
СпасиЫ Когда мы вернулись к рассмотрению вопроса, Дуг Макилрой заметил, что проблема и без того имела решение в С+е [АВМ): ПИИИИИИВ Множественное наследование 12.9. Инициализаторы членов и базовых классов При включении множественного наследования пришлось пересмотреть синтаксис инициализации членов и базовых классов. Например: с1авв Х : рцо11с А, 1цс хх; Х(гол а, 1цл 'о) А(а), // в()з), // хх(1) // ( )' рц)з11с в ( инициализации базового хласса А инициализации базового класса в инициализация члена хх Этот синтаксис инициализации параллелен синтаксису инициализации объектов класса: А х(1); в у(2); В то же время порядок инициализации по определению совпадал с порядком объявлений.