Б. Страуструп - Дизайн и Эволюция C++. 2006 (1160775), страница 59
Текст из файла (страница 59)
Еще отме~им, что действия, выполняемые «во время сборки мусора», могут осуществляться в любое время, начиная с момента последнего использования объекта и до завершения рабаты программы. Значит, на момент выполнения этих действий неизвестно состояние программы. Если же такаю рода действия все же необходимы, то проблему их выполнения в неопределенный момент уничтожения можно решить с помощью сервера регистрации. Когда абьекту нужна произвести какие-то действия в конце программы, он помещает свой адрес и указатель на функцию очистки в некоторый глобальный ассоциативный массив». Описанная модель, конечно, работоспособна, но, быть может, вызов деструкюров сборщиком мусора все же удастся сделать достаточно простым.
Это зависит оттого, какие объекты попадают в мусор и какие действия выполняют деструкторы. К сожалению, данная проблема пз числа тех, для которых трудно поставить реальный эксперимент, да и в других языках похожего опьгга, кажется, иет. Итак, я не думаю, что создать приемлемый для С++ механизм сборки мусора просто, но это пе невозможно. А если учесть, сколько людей думают пад данной проблемой, то наверняка скоро появится несколько решений. Глава 11.
Перегрузка Дьявол прячется в деталях. Анонимный автор 11.1. Введение Операторы призваны обеспечить удобство нотации. Рассмотрим формулу р=м*А (сила = масса * ускорение). В учебниках по элементарной физике эта формула не записывается в виде аее1дп ( р, лги1Сйр1у (М, А) ) ', Если переменные могут иметь разные типы, то нужно определить, разрешать ли смешанную арифметику или требовать явного приведения операндов к обшему типу. Например, если м имеет тип 1пс, а А — тип с1оц)э1е, то мы можем либо принять запись м*А и считать, что М нужно перед умножением привести к типу с)оц)э1е, либо потребовать от программиста писать нечто вроде с)оц)э1е (м) «А.
Для С++, как для С, Рогггап и почти всех языков, применяемых для вычислений, выбран первый подход. И это чревато определенными осложнениями. С одной стороны, пользователи хотят естественных преобразований «без протестов» компилятора, с другой — некорректные преобразования могут привести к сбою в работе программы. Если добавить сюда требование совместимости с довольно хаотичной системой встроенных типов и преобразований С, то становится понятно, что проблема действительно трудна. Стремление к гибкости и свободе выражения противоречит стремлению к безопасности, предсказуемости и простоте. В этой главе рассматривается, к каким усовершенствованиям механизмов перегрузки привел данный конфликт. 11.2.
Разрешение перегрузки Перегрузка имен функций и операторов в том виде, в каком она первоначально появилась в С++ (см. раздел 3.6), (агапэ(гцр, 1984Ь), завоевала популярность, но выявились и присущие механизму проблемы. В (Вггоцзггцр, 1989Ь) так резюмированы улучшения, введенные в версию 2.0: «Механизм перегрузки в С++ был пересмотрен.
Цель — обеспечить разрешение типов, которые раньше считались «слишком похожими», и добиться независимости от порядка объявлений. Получившаяся в результате схема более выразительна и позволяет выявить больше ошибок неоднозначности». ' Некоторые предпочли бы Р-МА, иа объяснение тога, как такая натация могла бы работать (перегрузка отсутствующего пробела), выходит за рамки этой книги. Разрешение перегрузки ПИИИИИИН Благодаря более детальному механизму разрешения появилась возможность перегружать имена, принимая во внимание различия между фпс и айат, б1оаг и ()оцЬ1е, сопэс и не-соцвс, а также различия между базовым и производным классами.
Независимость от порядка позволила избавиться от ряда ошибок. Ниже все аспекты перегрузки рассматриваются по очереди, также объясняется, почему ключевое слово очег1оа(1 больше не употребляется. 11.2.1. Детальное разрешение В первоначальном варианте правила перегрузки в С++ учитывали ограничения, присущие встроенным типам С [Кегп(81)ап, 1978): не было значений типа Е1оаг (точнее, гча1це такого типа), поскольку при вычислениях тип й1оаг сразу же расширялся до с)оцЬ1е. Аналогично не было и значений типа сЬаг, так как при каждом использовании спаг происходило расширение до 1пс.
Отсюда неловольство пользователей, вызванное невозможностью естественным образом создать библиотеку для вычислений с плавающей точкой одинарной точности, а также жалобы на то, что при работе с функциями, манипулирующими символами, легко сделать ошибки, которых можно было бы избежать. рассмотрим функцию вывода. Если мы не можем перегрузить ее на основе различия между 1пс и сЬаг, приходится заводить два имени.
В первоначальном варианте потоковой библиотеки (см. раздел 8.3.1.) были такие функции: овггееыа орегагог« (ьпг); // вывод ьпг (включая преобразованные // снег) в виде последовательности цифр овггевыь рпг(спаг с); // вывод айаг в виде символов Однако многие писали так: сопл« 'х'; г1оаг еЬв(г1оаг)) ооиЫе аЬв (с(опЫе); ьпг аЬв(ьпс); ипвьдпед аЬв(ппв1дпеб); сьаг аЬв(сьаг)у чоЫ Г() ( аЬв(1); аЬв()д)) аЬв(1.0); // аьв(1пс) // аЬв(ппвьдпед) // аЬв(бопЫе) н, естественно, удивлялись, почему выводится й й (числовое значение АЯСП 'Х'), а не обычный символ х. Для решения проблемы правила типов в С++ были изменены таким образом, что типы сЬаг и 81оаг не подвергались расширению в механизме перегрузки. Помимо этого тип символьного литерала, например, 'х' был определен как сьаг.
В то же время была принята появившаяся незадолго до этого в АЮ! С нотация для записи литералов типа цпв1дпес) и й1оас. Поэтому стала возможной следующая запись: ИИИИИИИВ Перегрузка аьв(1дк); // аьв(11оас) аЬв('а'); // аЬв(снаг) ) сьа * вгггох(сьаг*, сопвг сьаг*); сопвг сьаг* вгггох[сопвг сьаг*, сопвг сьаг*); в качестве альтернативы стандартной функции из библиотеки Ай)31 С сьаг* всггох(сопев сьаг*, сопвс сЬаг*); Функция вьгго)с() из библиотеки С возвращает подстроку константной строки, переданной в качестве первого аргумента. Описать данную подстроку без квалификатора с опас в С++ невозможно, поскольку это расценивается как неявное нарушение системы типов. С другой стороны, несовместимость с С должна быть сведена к минимуму, поэтому предоставление двух вариантов функции в с гс о)с представляется наиболее разумным вариантом.
Допущение перегрузки па основе наличия и/ш отсутствия сопвс — часть общей стратегии ужесточения правил употребления сопвс (см. раздел 13.3). Опыт показал, что при сопоставлении функций следует принимать во внимание иерархии, образованные в результате открытого наследования. При наличии выбора следует отдавать предпочтение преобразованию в самый низший в иерархии класс.
Аргумент чо(с(* выбирается лишь в том случае, если никакой другой указатель не подходит. Например: с1авв В ( /* ... */ ); с1авв ВВ : риь11с В ( /* ... */ ); с1авв вВВ ; риь1(с вв ( /* ... */ ьа 1(В*); чоьй Г (ВВ*); чоьд г (чоЫ*); рЬЬЬ, ВВ* рЬЬ, В* рЬ, гпг* Р() чо1о д(ввв* ( г(рьЬЬ); 1(рЬЩ; 1(рь): : (р!); // г[вв*) // 1(вв*) // 1[в") Г(чоха") В С принято, что символьный литерал ('а') имеет тнп Ьпс, Как ни странно, приписывание 'а' типа сьаг в С++ не приводит к проблемам совместимости.
Если не считать примера в(хеопс ( ' а ' ), то любая конструкция, которая может быть записана в С и С++, приводит к одному и тому же результату. При назначении символьному литералу типа сьаг я отчасти опирался па сообщение Майка Тимана об опыте использования в О)ч(У С++ флага компилятора, обеспечивающего такую интерпретацию. С другой стороны, выяснилось, что разницу между сопвь и не-сопвс можно успешно использовать. Наглядным примером такой перегрузки является пара функций «6ВИИИИИИ Разрешение перегрузки 11.2.2.
Управление неоднозначностью В первоначальном механизме перегрузки разрешение неоднозначностей зависело от порядка объявлений. Объявления рассматривались поочерелно, и предпочтение отдавалось тому, которое встретилось в тексте раньше. Чтобы избежать недоразумений, при сопоставлении принимались во внимание только преобразования, не сужающие тип. На»»ример: очегтоай чо»й рг»пс(ьпг); // перноначальные (ло 2.0) правила: чогй рг»пг(йопЫе); чогй д() ( рг1пг(2.0)» // ргьпг(йопЫе): рг»пс(2.0) // преобразование йопЫе->гас не // рассматривается // рг1пс (йопЫе): ргьпг (йопЫе (2 .