|
Программирование >> Полиморфизм без виртуальных функций в с++
void f(X a, XX b) { a.copy(&Ь); правильно: скопировать часть b, принадлежащую X b.copy(&а); ошибка: сору(Х*) скрыто за сору(ХХ*) Если разрешить вторую операцию копирования - а так и случилось бы в случае объединения областей действия, - то состояние объекта b изменилось бы лишь частично. В большинстве реальных ситуаций это привело бы к очень стран-но.му 1юведению объектов класса XX. Я видел, как люди попадались в эту ловушку при использовании компилятора GNU С++ (см. раздел 7.1.4), разрешающего такую перегрузку. Если функция сору () виртуальна, то можно считать, что XX: : сору () замещает X: : сору (), но тогда для нахождения ошибки с b. сору (&а) потребовалась бы проверка типов во вре.мя исполнения, и программистам пришлось бы страховаться от таких неприятностей (см. раздел 13.7.1). Это я понимал уже тогда и боялся, что есть и другие, не осознанные мной проблемы. Вот гючему пришлось остановиться на таких правилах, которые казались мне самыми строгими и одновременно наиболее простыми и эффективными в реализации. Теперь уже я подозреваю, что с помощью правил перегрузки, введенных в версии 2.0 (см. раздел 11.2.2), можно было справиться с этой ситуацией. Рассмотри.м вызов b. сору (&а). Переменная b по типу точно соответствует неявному аргу.мен-ту XX: : сору, но требует стандартного преобразования для соответствия X: : сору. С другой стороны, переменная а точно соответствует явному аргументу X: : сору, но требует стандартного преобразования для соответствия XX: :сору. Следовательно, при разрешенной перегрузке такой вызов был бы признан ошибочным из-за неоднозначности. О явном задании перегрузки функций в базовом и производном классах рассказывается в разделе 17.5.2. 3.6. Перегрузка Некоторые пользователи просили меня обеспечить возможность перегрузки операторов. Идея выглядела заманчиво, а по опыту работы с Algol68 я знал, как ее реализовать. Однако вводить понятие перегрузки в С++ мне не хотелось по следующим причинам: J реализовать перегрузку нелегко, а компилятор при этом разрастается до чудовищных размеров; □ научиться правильно пользоваться перефузкой трудно и столь же трудно корректно определить ее. Следовательно, руководства и учебники тоже разбухнут; □ код, в котором используется перегрузка, принципиально неэффективен; □ перегрузка затрудняет понимание программы. Если признать верными 3-й и 4-й пункты, то С++ лучше было бы обойтись без перегрузки. А если истинны были бы 1-й или 2-й, то у меня не хватило бы ресурсов для поддержки перегрузки. Однако если все эти четыре утверждения являлись бы ложными, то перегрузка помогла бы решить множество проблем, возникавших у пользователей С++. Некоторые хотели, чтобы в С++ имелись комплексные числа, матрицы и векторы в духе APL, многим для работы пригодились бы массивы с проверкой выхода за границы диапазона, много.мерные массивы и строки. Мне были известны, по крайней мере, два разных приложения, где было бы полезно перегрузить логические операции типа I (или), & (и) и (исключающее или). Список можно было бы продолжать, и с ростом числа пользователей С++ он только расппфялся бы. На пункт 4 - перегрузка зату.манивает?> код - я отвечал, что некоторые из моих друзей, чье мнение я ценил и чей опыт программирования насчитывал десятки лет, утверждают, что их код от перегрузки стал бы только понятнее. Что с того, если, пользуясь перегрузкой, вы написали невразу.мительную програм.му? В конце концов, это можно сделать на любо.м языке. Важно лишь научиться использовать то или иное средство во благо, а не во вред. Затем я убедил себя, что перефузка не обязательно должна быть неэффективной [Stroustrup, 1984b], [ARM, §12.1с]. Детали .механизма перегрузки были в основном проработаны .мной, а также Стью Фельдманом, Дугом Макилрое.м и Джонатаном Шопиро. Итак, получив ответ на третий вопрос о якобы принципиальной неэффективности кода, в котором используется перегрузка , нужно было ответить на первый и второй - о сложности компилятора и языка. Сначала я отметил, что пользоваться классами с перегруженными операторами, напри.мер complex и string, было очень легко. Затем написал разделы руководства, дабы доказать, что дополнительная сложность вовсе не так серьезна, как кажется. Этот раздел занял меньше полутора страниц (в 42-страничном руководстве). Наконец, за два часа была написана первая реализация, которая заняла в Cfront всего 18 лишних строк. Таким образом, я достаточно убедительно продемонстрировал, что опасения относительно сложности определения и реализации перегрузки преувеличены. Впрочем, в главе 11 показано, что некоторые проблемы все же имеют место. Естественно, любой вопрос я решал, сначала доказывая полезность того или иного нововведения, а затем реализуя его. Механизмы перегрузки детально рассмотрены в работе [Stroustrup, 1984b], а при.меры их использования в классах -в [Rose, 1984] и [Shopiro, 1985]. Полагаю, перегрузка операторов была для С++ хорошим приобретением. По-МИ.МО очевидного использования перегруженных арифметических операторов (+, *, +=, *= и т.д.) в численных приложениях, для управления доступо.м часто перефужают операторы взятия индекса [], применения () и присваивания =, а операторы << и стали стандартными операторами ввода/вывода (см. раздел 8.3.1). 3.6.1. Основы перегрузки Нижеприведенный пример иллюстрирует базовый способ применения пере-фузки: class complex { double re, im; public: complex(double); complex(double,double); friend complex operator+(complex, complex); friend complex operator*(complex, complex); ... С по.мощью этого класса простые выражения с комплексными числами преобразуются в вызовы функций: void f(com.plex zl, complex z2) { complex z3 = zl+z2; operator+(zl,z2) По умолчанию присваивание и инициализацию определены как почленное копирование (см. раздел 11.4.1). Проектируя механизм перефузки, я полагался на преобразования, чтобы уменьшить число перегружаемых функций. Например: void g(complex zl, complex z2, double d) { complex z3 = zl+z2; operator+(zl,z2) complex z4 = zl+d; operator+(zl,complex(d)) complex z5 = d+z2; operator+(complex(d),z2) Иными словами, неявное преобразование double в complex позволяет поддержать смешанную арифметику с помошью одной функции сложения комплексных чисел. Для повышения эффективности и точности вычислений можно было бы ввести дополнительные функции. Мы могли вообще обойтись без неявных преобразований, если бы потребовали, чтобы преобразование всегда выполнялось явно, или предоставили полный набор функций сложения: class complex { public: friend complex operator+(complex, complex); friend complex operator+(complex, double); friend complex operator+(double, complex); ... Язык без неявных преобразований стал бы проще, не было бы места злоупотреблениям ими. К тому же вызов с использованием неявного преобразования обычно менее эффективен, чем вызов функции с точно соответствующими типами аргументов. Рассмотрим четыре базовых арифметических операции. Чтобы определить полный набор смешанных операций над числами типа complex и double, требуется 12 арифметических функций. И всего 4 функции плюс один конвертор, если использовать неявные преобразования. Если же число операций и типов больше, то разница между линейным росто.м числа функций в случае применения
|
© 2006 - 2024 pmbk.ru. Генерация страницы: 0
При копировании материалов приветствуются ссылки. |