Б. Страуструп - Дизайн и Эволюция C++. 2006 (1160775), страница 75
Текст из файла (страница 75)
Другим словами, ослабление позволяет продолжать важную работу, оставаясь в рамках системы типов и це прибегая к приведениям. Итак, ослабление правила Оказалось, в реальных программах это мешает. Также было отмечено, что правило, согласно которому замещающая функция должна иметь в точности тот же тип, что и замещаемая, можно ослабить, нс нару)пая систему типов и нс усложняя реализацию. Например, можно было бы разрешить такое: ЗИИИИИИБ Ослабление правил замещения 13.7.1. Ослабление правил аргументов По моему опыту, ослаблению правила замещения для типа возвращаемого значения неизменно сопутствует неприемлемое «зквивалентное» предложение ослабить правила для типов аргументов. Например: с1авв Рьд ( риЫго: ч1гсиа1 гпс орегагог==(сопел Р1да); /! ): с1авв Со1Р1д: риЫ1с Рьд ( роЫгс: // Предполагается, что Со1Р1д::орегасог==() // замещает Р1д::орегасог==() // (не Разрешено в С++) 1пг орегасог==(сопел со1Р1да х) // рг1часе: Со1ог со1; ): 1пс со1Р1д::орегагог==(сопел со1Р1да х) гегогп со) == х.со1 ав Ргд::орегагог==(х); ) Выглядит привлекательно и позволяет писать неплохой код, например: лота Г(Р1да ггд, Со1Р1да сг1, Со1Р1да сг2) ( (11д==с11) ( // сравнить объекты Рсд // ) е1ве 11 (с11==с12) ( // сравнить объекты Со1Р1д О о типах значений, возвращаемых замещаюшими функциями, оказывалось в русле моих усилий сделать программирование на С++ более безопасным, простым и декларативным.
Данное средство не только устраняло много обычных приведений типов, но также препятствовало искушению злоупотребить новыми динамическими приведениями, которые обсуждались в то же самое время гсм. раздел 14.2.3). Рассмотрев несколько вариантов, мы разрешили замещение В* на (г' и Ва на ра, где в — достижимый базовый класс для г). Кроме того, можно добавлять или убирать сопя с, если зто безопасно. Не допускались такие технически возможные преобразования, как () в достижимый базовый класс в, г) — в класс х, для которого есть преобразование из (), 1пс* в чо1с1*, с1оц))1е в 1пг и т,д.
Нам казалось, что преимущества подобных преобразований не перевешивают стоимости реализации и потенциального запутывания пользователей. Уточнения понятия класса НИИИИВИ11 К сожалению, такое написание ведет и к неявному нарушению системы типов: чо1с) д(Р1да 11д, Со1Р1да с2) ( (сг==г1д) ( // что сравнивается? // Если со1Р1д:: орегасог== () замещает Г1д:: орегасог== (), то при выполнении сравнения сй==й1д будет вызван оператор Со1Г1д:: орегасог== () с аргументом типа Ид.
Это неудачно, так как со1Р1д:: орегасог== () обращается к члену со1, а в классе Р1д нет такого члена. Если бы для Со1г гд:: орегасог== ( ) было бы решено изменить его аргумент, то испортилась бы память. Я рассматривал такой сценарий, когда только начинал проектировать правила для виртуальных функций, и счел его неприемлемым.
Если бы данное разрешение было позволено, пришлось бы проверять каждый аргумент виртуальной функции во время выполнения. Устранить такие проверки во время оптимизации кода было бы нелегко. Без проведения глобального анализа мы никогда не узнаем, не объявлен ли объект в каком-то другом файле, причем с типом, для которого замещение потенциально опасно.
Затраты, связанные с такой проверкой, довольно су|цественны. Кроме того, если каждый вызов виртуальной функции может в принципе возбуждать исключение, то пользователю надо быть к этому готовым. Я счел, что не смогу пойти на такой шаг. В качестве альтернативного решения можно поручить программисту явно проверять, когда для аргумента типа производного класса необходимо использовать другую ветвь обработки. Например: с1авв Р1диге ( риЫ1с: у1ггпа1 гпг орегагог==[сопев Р1догеа); // ): с1авв Со1<1д : ришвс Г1диге ( риы1с: гпг орегагог==(сопев Р1дигеа х); // рг1наге: Со1ог со1; ): гпс Со1Г1д::орегагог==(сопев Ртдигеа х); ( 11 (Гсдиге::орегасог==(х)) ( сопев Со1Г1д* рс = с)упав1с сваг<сопев Со1Р1д*>(ах); 12 (рс) гегигп со1 == рс->со1; ) гегвгп Сч ЛИИИИИЕИП Мультиметоды При таком подходе проверяемое во время выполнения приведение с помощью дупашйс саяе (см. раздел 14.2.2) служит дополнением к ослабленным правилам замещения.
Ослабление позволяет безопасно и декларативно работать с типами возвращаемых значений; дупапйс саяе не препятствует явной и относительно безопасной работе с типами аргументов. 13.8. Мультиметоды Я неоднократно возвращался к механизму вызова виртуальных функций на базе более чем одного объекта. Часто его называют механизмом мультиметодов. Отказался я от мультиметодов с сожалением, поскольку идея мне нравилась, но найти для нее приемлемую форму не получалось. Рассмотрим пример; с1аяя Бпаре ( // с1аяя аесгапд1е : рпЫтс Бьаре ( )' с1аяя С(гс1е : риЫ(с БЬаре ( // ): Как можно было бы спроектировать функцию пересечения двух фигур 1пгегвесг (), которая бы корректно вызывалась для обоих своих аргументов? Например: вовс) 1(сьгс1еа с, БЬареа я1, аесгапд1еа г, Бьареа я2) ( 1пгегяесг(г,с); 1пгегвесг(с,г); тпгегяесг[с,я2); 1пгегяесг(я1,г); 1псегяесс(г,я2); (пгегяесг(я1,с); тпгегяесг(я1,я2); ) Если г и я относятся соответственно к Сйгс1е н БЬаре, то хотелось бы реализовать 1псегяесс четырьмя функциями: Ьоо1 (пгегяесс(сопев Стгс1еа, сопяг Стгс1ея); Ьоо1 тпгегяесг(сопев С1гс1еа, сопев яессапд1еа); Ьоо1 1пгегяесг(сопев аессапд1еа, сопев С1гс1ея)у ьоо1 тпгегяесг(сопев аессапд1еа, сопев аесгапд1еа); Каждый вызов должен был бы обращаться к нужной функции точно так же, как в случае с виртуальными функциями.
Но правильную функцию следует выбирать на основе вычисляемых во время выполнения типов обоих аргументов. Фундаментальная проблема, как мне виделось, заключалась в том, чтобы найти: ИИИИИИИ6" Уточнения понятия класса о механизм вызова, который был бы таким же простым и эффективным, как поиск в таблице виртуальных функций; з набор правил, которые позволяли бы разрешать все неоднозначности на этапе компиляции.
Видимо, эта проблема разрешима, но мое внимание никогда пе было сосредоточено на ней столь длительное время, чтобы успеть продумать все детали. Если я хотел, чтобы программа работала быстро, то для получения эквп валс ~гга таблицы виртуальных функций требовалось, судя по всему, очень мноп) памяти. Любое жс рсшснпс, нс расходуюшее «впустую» много памяти на дублирование элементов таблиц, либо медлешю осуществлялось, либо имело нспрсдсказуемыс характеристики эффективности, либо то н другое вместе. Например, кажлая рсалпзапия примера с с1гс1е и нессапд1е, нс трсбуклцая поиска подхоляшей функции во время выполнения, нуждалась в четырех указателях на функции, Дооавим сшс класс Тгйап91е — н требуется уже девять указателей.
Произведем от с1 гс1е класс Бп11еу — и необходимо целых шсстнадпать указателей, хотя семь из них следовало бы сэкономить, используя элементы таблш(ы, где вместо Бгв11еу стоит С1гс1е. Волос того, массивы указателей па функции, эквивалентные таблицам виртуальных функций, нельзя было построить, пока не станет известна вся программа, то ссть на стадии компоновки.
Причина в том, что нс сушсствуст одного класса, которому принадлежат все замсшаюшие функции. Да п ис может его быть, так как любая замешаюшая функция зависит от двух или более аргументов. В то время проблема была неразрешима, поскольку я не хотел вводить в язык средств, требующих нстривиальной поддержки со стороны комцоновщика. Однако, как оказалось впоследствии, такой поддержки можно дожидаться годами. Меня беспокоил — хотя и не казался неразрешимым — вопрос о том, как разработать схему, синтаксис которой был бы однозначным. Очевидный ответ таков: все вызовы мультимстодов должны подчиняться таким жс правилам, как и любыс другие вызовы.
Олнако такое рсзпение ускользало от меня, поскольку я искал какой-то специальный синтаксис и специальные правила Лля вызова мультимстодов. Например: (гэв)->ьп егвесг(); // вместо ьпсегвесг(г,в) гораздо лучшее решение было предложено Дугом Лса )йса, 1991~. Позволим янно объявлять аргументы со словом уйгг оа1. Например: Ьоо1 1псегвесс(»1гсиа1 сопев Яцареа, чьггиа1 сопев Япареь); Замсщаюшсй можст быть любая функция, имя и типы аргументов которой сопоставляются с учетом ослабленных правил соответствия в стиле тех, что приняты для типа возвращаемого значения.
Например: ьооь гпсегвесс(попас сьгс1еа, сопев нессапо1еа) // вамеяает ( О ) 11ИИИИИИШИ Мультиметоды 13.8.1. Когда нет мультиметодое Так как же написать функцию, вроде 1псегвесс ( ), без мультиметодов? До появления идентификации типа во время исполнения (см. раздел 14.2) единственным средством динамического разрешения на основе типа были виртуальные функции.
Поскольку нам нужно разрешение на основе двух аргументов, то виртуальную функцию придется вызывать дважды. В приведенном выше примере с С1гс1е и Пессапд1е есть три возможных статических типа аргумента, поэтому вюжно предоставить три виртуальные функции: с1авв Янаре // ч1гспа1 Ьоо1 гпгегяесг(сопев япареа) сопев =О; чггвяа1 ьоо1 1пвегяесс(сопев несгапд1еа) сопяг =О; ч1гспа1 ьоо1 1пгегяесг(сопев с1гс1еа) сопвг =О; ); В производных классах зти функции замещаются: с1аяв Нссгапд1е ( Ьоо1 1пгегвесг(сопят ЯЬареь) сопят; Ьоо1 (пгегвесг(сопьг кесгапд1еь) сопят; Ьоо1 гпгегвесг(сопя С1гс1еь) сопев; Любой вызов 1псегвесс() разрешается вызовом подходящей функции из классов сйгс1е или пессапд1е. Затем надо позаботиться о том, чтобы функция, принимающая неконкретизированный аргумент типа ЯЬаре, вызывала другую виртуальную функцию для замещения его более конкретным: ьоо1 весгапд1е::1пгегвесг(сопев яьареа я) сопяг ( гегпгп я.гпгегвесг(*гЬ1я); О *гьтв - зто кесгапд1е: // РазРешаем в зависимости от я Ьоо1 сггс1е::ьпсегяесг(сопев япареь в) сопят И наконец, мультиметоды можно вызывать с помощью обычного синтаксиса вызова, как показано выше.
Мультиметоды — один из интересных вопросов вроде ччто, если...в в С++. Мог бы я в то время спроектировать и реализовать их достаточно хорошо? Было бы их применение настолько важным, чтобы оправдать потраченные усилия? Какую работу пришлось бы оставить незавершенной, чтобы хватило времени на проектирование и реализацию мультиметодов? Примерно с 1985 г.
я сожалею, что не включил зто средство в язык. Бдинственный раз я официалыю упомянул о мультимстодах па конференции ООРБЕА, когда выступил против языковой нетерпимости и бессмысленности ожесточенных баталий по поводу языков 151гопвтгцр, 1990~. Я привел в качестве примера некоторые концепции иа языка С1.08, которые мцс чрезвычайно нравились, и особо выделил мультиметоды. ППИИИИИИВ Уточнения понятия класса ( гегпгп я.тпгегяесс(*гь1я); // *льве — зто стгс1е: О разрепаем в зависимости ог я ) Остальные функции 1псегяесс () просто выполняют то, что от них требуется, «зная» типы обоих аргументов. Заметим, что для применения такой техники необходима лишь первая функция Бьаре:: 1псегяесс () .