|
Программирование >> Полиморфизм без виртуальных функций в с++
в уже имеющийся контекст. Например, в библиотеке С существует множество функций, принимающих строковые аргументы типа char*. Когда Джонатан Шопиро впервые разрабатывал полнофункциональный класс String, он обнаружил, что ему либо придется продублировать все функции следующего вида: int strlen(const char*); исходная С-функция int strlenlconst String&); новая С++-функция либо предоставить преобразование из String в const char*. Поэтому я ввел в С++ понятие операторной функции преобразования (конвертора): class String { . . . operator const char*(); ... int strlenlconst char*); исходная С-функция void f(Strings s) { . . . strlen(s); strlen(s.operator const char*()) . . . Ha практике пользоваться неявны.ми преобразованиями иногда бывает трудно. Однако написание полного набора операций для смешанных вычислений -тоже не самый легкий путь. Я бы хотел найти лучшее решение, но из всех, что знаю, неявные преобразования - наименьшее зло. 3.6.4. Перегрузка и эффективность Вопреки наивному предрассудку, между операциями, записанными в виде вызовов функций и в виде операторов, нет фундаментального различия. С эффективностью перегрузки были и остаются актуальными вопросы другого рода: как реализовать встраивание и избежать создания лишних временных объектов. Сначала я отметил, что код, сгенерированный для выражения типа а+Ь или v[i], идентичен тому, который получается при вызове функций add{a,b) и v.elem{i). Далее я обратил внимание на то, что, пользуясь встраиванием, программист может ликвидировать накладные расходы (время и память) на вызов функции. И наконец, пришел к выводу, что эффективная поддержка такого стиля программирования для больших объектов требует вызова по ссылке (подробнее об этом см. раздел 3.7). Оставалось только решить, как избавиться от лишних копирований в конструкциях вроде а=Ь+с. Генерирование кода вида assign(add(Ь,с),t); assign(t,а); неудачно по сравнению с кодом add and assign(b,c,a); который компилятор генерирует для встроенного типа, а программист может написать явно. В конце концов я продемонстрировал, как сгенерировать код вида add and initialize(b,c,t); assign(t,a); При этом остается еще одно лишнее копирование. Избавиться от него можно, только когда удается доказать, что операции + и = не зависят от значения, которое присваивается (совмещение имен - aliasing). Более понятное объяснение aroii оптимизации - в Cfront ее не было вплоть до версии 3.0 - см. в [ARM]. Полагаю, что первой реализацией, где использовалась подобная техника, был комшьчятор фирмы Zortech. Уолтер Брайт легко реализовал ее после нашей беседы в 1990 г. после завершения рабочей встречи комитета по стандартизации ANSI С++. Я считал такую схе.му прие.млемой, поскольку для ручной оптимизации большинства распространенных операций лучше подходят такие операторы, как +=, а также потому, что при инициализации можно принять отсутствие сов.мещения имен. Позаимствовав из Algol68 идею о том, что объявить переменную допусти.мо в точке, где она впервые используется (а не в начале блока), я мог поддержать идиомы только инициализация или единствешюе присваивание , которые более эффективны и менее подвержены ошибкам, чем традиционная техника, когда значение переменной присваивается многократно. Например, можно написать complex compute(complex z, int i) { if (/*... V ) { . . . complex t = f(z,i); . . . z += t; . . . return t; вместо complex compute(complex z, int i) { complex t; if ( /* ... V ) { ... t = f(z,i); . . . z = z + t; . . . return t; Еще одна идея о том, как повысить эффективность за счет устранения временных объектов, изложена в разделе 11.6.3. 3.6.5. Изменение языка и новые операторы я считал важным ввести перегрузку так, чтобы расширить язык, а не изменить существующую семантику. Иначе говоря, должна быть возможность определять операторы над пользовательскими типами (классами), но не изменять смысл операторов над встроенными типами. Кроме того, я не хотел позволять программисту вводить новые операторы. Мне не нравились загадочность нотации и необходимость сложных стратегий синтаксического анализа вроде тех, что применяются в Algol68. Думаю, в этом вопросе моя сдержанность была оправданной. См. также разделы 11.6.1 и 11.6.3. 3.7. Ссылки Ссылки были введены в основном для поддержки перегрузки операторов. Дуг Макилрой вспоминает, что однажды я объяснял ему некоторые проблемы, касавшиеся схемы перегрузки операторов. Он употребил слово ссылка , после чего я, пробормотав спасибо , выбежал из его кабинета, чтобы на следующий день появиться с практически готовым решением, которое и вошло в язык. Просто Дуг тогда напомнил мне об Algo!68. В языке С аргументы всегда передаются функции по значению, а в случаях, когда передача объекта по значению не годится или слишком неэффективна, программист может передать указатель на объект. При это.м нотация должна быть удобной, поскольку нельзя ожидать, что пользователь всегда будет вставлять оператор взятия адреса для больших объектов. Так, а = b - с; это общепринятая нотация, в то время как а = &Ь - &с; нет. Как бы то ни было, &Ь-&с уже имело в С определенную семантику, и менять ее я не хотел. После инициализации ссылка уже не может ссылаться ни на что другое. Иными словами, коль скоро она инициализирована, нельзя заставить ее ссылаться на другой объект, то есть нельзя изменять привязку. В прошлом меня неприятно удивил тот факт, что в Algol68 предложение г1=г2 может означать либо присваивание объекту, на который ссылается rl, либо изменение значения самой ссылки rl (повторная привязка) в зависимости от типа г2. Я хотел уйти от таких проблем в С++. Если необходимы более сложные манипуляции, всегда можно воспользоваться указателями. Поскольку в С++ есть и ссылки, и указатели, ему не нужны средства для различения операций над самой ссылкой и операций над объектом, на который она ссылается (как в Simula). Не нужен и дедуктивный механизм, применяемый в Algol68.
|
© 2006 - 2024 pmbk.ru. Генерация страницы: 0
При копировании материалов приветствуются ссылки. |