Б. Страуструп - Дизайн и Эволюция C++. 2006 (1160775), страница 24
Текст из файла (страница 24)
Поэтому я ввел в Се+ понятие операторной функции преобразования (конвертора): с1авв ЯСггпд ( // орегасог сопвс спас*(); // 1пС вкг1еп(сопвС сьас*); // исходная с-функция кого Г(яСгкпда в) // вкг1еп(в)> // всг1еп(в.оресаСог сопвс спас"()) О ) На практике пользоваться неявными преобразованиями иногда бывает трудно.
Однако написание полного набора операций для смешанных вычислений— тоже не самый легкий путь. Я бы хотел найти лучшее решение, но из всех, что знаю, неявные преобразования — наименьшее зло. 3.6А. Перегрузка и эффективность Вопреки наивному предрассудку, между операциями, записанными в виде вызовов функций и в виде операторов, нет фундаментального различия.
С эффективностью перегрузки были и остаются актуальными вопросы другого рода: как реализовать встраивание и избежать создания лишних временных объектов. Сначала я отметил, что код, сгенерированный для выражения типа аеЬ или у [1], идентичен тому, который получается при вызове функций ас]с](а, Ь) и ц. е1есп (1 ) . Далее я обратил внимание на то, что, пользуясь встраиванием, программист может ликвидировать накладные расходы (время и память) на вызов функции.
И наконец, пришел к выводу, что эффективная поддержка такого стиля программирования для больших объектов требует вызова по ссылке (подробнее об этом см. раздел 3.7). Оставалось только решить, как избавиться от лишних копирований в конструкциях вроде а=Ь+с. Генерирование кода вида аз в1дп (асЫ (Ь, с ), С ); авв1дп (С, а); МИИИИИИП Перегрузка неудачно по сравнению с кодом асЫ апа ааа1оп(Ь,с,а); который компилятор генерирует для встроенного типа, а программист может написать явно. В конце концов я продемонстрировал, как сгенерировать код вида асК) апо 1пгсьа11хе(Ь,с,с); ааа1оп)с,а); соар1ех соврпге)соар1ех г, 1пС 1) ( 11 ) /* */ ) ( // ) сотр1ех С = Г(х,1); // х // гегпгп с; вместо сопр1ех саврасе(сопр1ех э, гпс 1) 1 сопр1ех с; 11 ( /* ... */ ) ( // ) с = а<а,1); // е = х // геспгп с; При этом остается еще одно «лишнее» копирование.
Избавиться от него можно, только когда удается доказать, что операции» и = не зависят от значения, которое присваивается (совмещение имен — а1)аэ)пя). Более понятное объяснение этой оптимизации — в С1гопс ее не было вплоть до версии 3.0 — см. в )АКМ). Полагаю, что первой реализацией, где использовалась подобная техника, был компилятор фирмы ХогеесЬ. Уолтер Брайт легко реализовал ее после нашей беседы в 1990 г.
после завершения рабочей встречи комитета по станлартизации АЮ1 С+-». Я считал такую схему приемлемой, поскольку для ручной оптимизации большинства распространенных операций лучше подходят такие операторы, как +=, а также потому, что при инициализации можно принять отсутствие совмещения имен. Позаимствовав из А1яо168 идею о том, что объявить переменную допустимо в точке, где она впервые используется (а не в начале блока), я мог поддержать идиомы «только инициализация» или «единственное присваивание», которые более эффективны и менее подвержены ошибкам, чем традиционная техника, когда значение переменной присваивается многократно.
Например, можно написать ИЙИИИИИИ Рождение С++ Еше одна идея о том, как повысить эффективность за счет устранения временных объектов, изложена в разделе 11.6.3. 3.6.5. Изменение языка и новые операторы Я считал важным ввести перегрузку так, чтобы расширить язык, а не изменить существую шую семантику. Иначе говоря, должна быть возможность определять операторы над пользовательскими типами (классами), но не изменять смысл операторов над встроенными типами.
Кроме того, я не хотел позволять программисту вводить новые операторы. Мне не нравились загадочность нотации и необходимость сложных стратегий синтаксического анализа вроде тех, что применяются в А!8о168. Думаю, в этом вопросе моя сдержанность была оправданной. См. также разделы 11.6.1 и 11.6.3. 3.7. Ссылки Ссылки были введены в основном для поддержки перегрузки операторов. Дуг Макилрой вспоминает, что однажды я объяснял ему некоторые проблемы, касавшиеся схемы перегрузки операторов. Он употребил слово «ссылка», после чего я, пробормотав «спасибо», выбежал из его кабинета, чтобы на следующий день появиться с практически готовым решением, которое и вошло в язык.
Просто Дуг тогда напомнил мне об А!8о!68. В языке С аргументы всегда передаются функции по значению, а в случаях, когда передача объекта по значению не годится или слишком неэффективна, и рограммист может передать указатель на объект. Г1ри этом нотация должна быть удобной, поскольку нельзя ожидать, что пользователь всегда будет вставлять оператор взятия адреса для больших обьектов. Так, это общепринятая нотация, в то время как а = аЬ вЂ” ас; нет.
Как бы то ни было, аЬ-ас уже имело в С определенную семантику, и менять ее я не хотел. После инициализации ссылка уже не может ссылаться ни на что другое. Иными словами, коль скоро она иницнализирована, нельзя заставить ее ссылаться на другой объект, то есть нельзя изменять привязку. В прошлом меня неприятно удивил тот факт, что в А!8о!68 предложение г1= к2 может означать либо присваивание объекту, на который ссылается г1, либо изменение значения самой ссылки г1 (повторная привязка) в зависимости от типа г2. Я хотел уйти от таких проблем в С«~-. Если необходимы более сложные манипуляции, всегда можно воспользоваться указателями. Поскольку в С»» есть и ссылки, и указатели, ему не нужны средства для различения операций над самой ссылкой и операций над объектом, на который она ссылается (как в Рйшп1а).
Не нужен и дедуктивный механизм, применяемый в А!8о168. Ссылки 1МИИИИИИ Однако я сделал одну серьезную ошибку, разрешив инициализировать неконстантную [без спецификатора с опз с) ссылку значением, не являющимся 1ча1ие. Например: чагг) 1псг(1пса гг) ( гг++; ) чоьд д() г)оп)зге вв = 1; 1псг(вв); // примечание: передано ооп)зге, ожидалось 1пг // исправлено в версии 2.0 ) ехгегп "рогггап" г1оаг вдгг (сопев г1оага) г чоМ г() ( вдгг(2); ) // вазов с передачей аргумента по ссылке Помимо очевидных применений ссылок [для передачи аргументов), мы считали важной возможность использовать ссылки как возвращаемые значения. Это позволило легко написать оператор взятия индекса для класса ясгйпд: с1авв Ясгапд ( с)гага орегасог(](!пс 1пс)ех); // оператор взятия индекса // возвращает ссылку човц г(ЗГг1пда в, гпс 1) ( с)гаг с1 = в[1]; в[1] = с1; // // присвоить результат орегасог[] // присвоить результату орегасог[] Из-за различия типов упса не может ссылаться на переданное значение типа г]ои]з1е, поэтому генерировалась временная переменная для хранения значения типа 1пс, инициализированная значением вв.
В связи с этим функция 1псг модифицировала данную временную переменную, а вызывающая функция получала неизмененное значение. Я разрешил инициализировать ссылки не-1ча1пе, чтобы сделать различие между вызовом по значению и по ссылке деталью реализации вызываемой функции, которая вызывающей вообще неинтересна. Для константных ссылок зто возможно, для неконстантных — нет. В версии 2.0 определение С++ было соответствующим образом изменено.
Следует отметггть, что констигтную ссылку можно инициализировать не-1ча1пе, а также 1ча1ие, для типа которой требуется преобразование. В частности, зто позволяет вызывать написанную на гог(гап функцию с аргументом-константой: ИИИИИИИВ Рождение С++ Возврат ссылки на внугреннее представление Яс г1пд предполагает, что пользователи ведут себя ответственно.
3.7.1. Ыа!ие и Я)гаlие Если орегагог [] () реализован так, что возвращает ссылку, то не удастся приписать различную семантику чтению и записи элемента, извлеченного по индексу. Например, в выражении я1(1] = в2(1]; мы не можем одновременно произвести действие над строками я1 и я2, где в одном случае значение присваивается, а в другом — читается. Размышляя над задачами реализации строк с разделяемым представлением и доступа к базам данных, мы с Джонатаном Шопиро пришли к выводу о необходимости иметь различную семантику для доступа на чтение и на запись.
В обоих случаях чтение — это простая и дешевая операция, тогда как запись — потенциально дорогая и сложная, и для нее может потребоваться копирование структур данных. Рассматривались две возможности: с1аяя сйах хек ( /2 идентификация символа в строке йхфелх) с1аяв Ягхфла; [лх 1; Бхх1лч* я; снах хе~(эхх[лд* яя, [лх 11); ( я=вя; 1=11; ) ри)э11с: иоЫ орехахох=(снах с); орехахох снах(); Присваивание объекту типа с]тат геб реализовано как присваивание символу, на который он ссылается, а чтение объекта типа с]таг геб — как преобразование в тип сцаг, возвращающее значение символа: чоЫ снах хек::орегасох=(снах с) ( я->г(1]=с; ) снах хег:".орехагох снах() ( гегцхп я->х(1]; Заметим, что только Яхг1пд может создать с]таг геб.
Фактическое присваивание реализуется классом ясг1пд: с1аяя Ягх1лд хх1елс с1аяя снах хех; снах* х; о определить различные функции для применения к 1ча1це и гча1це; с) заставить программиста использовать вспомогательную структуру данных. В результате был выбран второй подход, поскольку он помогал избежать расширения языка, а кроме того, мы считали возврат объекта, описывающего положение в контейнерном классе вроде ясг1пд, более общим приемом.
Основная идея заключалась в том, чтобы иметь вспомогательный класс, который определяет позицию в контейнерном классе почти как ссылку, но имеет разную семантику для чтения и записи. Например: Константы ПИИИИИИИ рцЫус: с)тат гет орегатог [] (1пт 1) ( гегцгп с)тат ге1(т)т1в,1)г ) // При наличии таких определений предложение з1[1) = з2[1); означает з1.орегвтог[](т) = в2.орегатог[](1) где в1.