Бьерн Страуструп. Язык программирования С++. Специальное издание (2011) (1004033), страница 69
Текст из файла (страница 69)
Меныаее сокрытие операций приводит к тому, что встроенные операции всегда доступны и что пользователи могут придавать операциям новый смысл без модификации сушествуюших определений классов. Например, в стандартной библиотеке операция вывода «определена по отношению ко встроенным типам. Пользователь же всегда может доопределить эту операцию для вывода своих собственных классовых типов без какой бы то ни было модификации класса оаггеат 521.2.1). 11.3. Тип комплексных чисел Реализация концепции комплексных чисел, представленная во введении, слишком ограничена, чтобы подойти для реальной работы. Например, заглянув в книгу по математике, мы ожидаем, что следующее будет работать: го(а 3'( ) сагир!ах а = сотр!ах (1, 2) сотр!ех Ь = 3; сотр(ах с = а+2. 3; сагир!ех а =- 2+Ь г сотр!ех с = -Ь-с; Ь = с*2*с; Кроме того, мы ожидаем, что реализованы такие операции, как операция == для сравнения комплексных чисел и операция «для вывода, а также набор подходяших математических функций, таких как яа() и з()гг().
Класс сотр!ех является конкретным типом, так что будем следовать рекомендациям из 8 !0.3. Кроме того, поскольку стандартная комплексная арифметика опирается на применение операций, то нам придется в рамках типа сошр!ех использовать все, что уже известно про перегрузку операций. 11.3.1. Перегрузка операций функциями-членами и глобальными функциями Я прелпочитаю минимизировать количество функций, непосредственно манипулируюших внутренним представлением объекта класса. Для этого в теле класса следует определять лишь те операции, которые по своей внутренней природе должны изменять состояние первого операнда, например ч=. Любую иную операцию, 11.3.
Тип комплексных чисел целью которой является порождение нового значения, операцию» к примеру, сле- дует реализовывать вне класса (при этом она может опираться на фундаментальные операции, имеющие доступ к представлению класса): с1аев сотр1ех ) с(оиЫе ге, ии( риЫ(с: сотр!ехь орегасог»= (сотр!сх а) ( //имеет доступ к внутреннему представлению //... сотр!ех орега(ог'- (сотр!ех а, сотр!ех Ь) ) сотр!ех г = а( гесигп г»= — Ь( Отталкиваясь от данных определений, мы можем писать: сопср!ех е) // г! = орегасог»(х,прего!ог»(ух)) //г2 = х // г2.орегасог+=(у) // г2. арес асог+ =(е) Составные операции присваивания, такие как += и *=, программировать легче, чем их более «простые составные части» вЂ” операции + и *.
Поначалу это многих удивляет, но это вытекает из того факта, что три объекта вовлекаются в работу операции + (два операнда и временный объект-результат), в то время как у операции += — всего два объекта. В последнем случае достигается более высокая эффективность за счет устранения необходимости во временных объектах. Например, следующий код !п!!пе соспр1ехь сотр!ех:: орега(ог«= ) сотр!ех а) 1 ге += а.ге( !т»= а.ипс ге(ипс *(Ыв( ) не требует временных объектов для хранения результатов сложения н упрощает компилятору задачу по реализации встраиваемого кода. Оптимизирующий компилятор сумеет сгенерировать код, близкий к оптимальному, и для операции +.
К сожалению, не каждый компилятор может похвастаться блестящими оптимизирующими способностями, и не каждый пользовательский тип столь же прост как тип сошр1ех; поэтому в В)! .5 обсуждаются синтаксические средства, позволяющие операциям иметь прямой доступ к представлению класса. гоЫ1" (совр!ех х, сотр!ех у, сотр!ех г! = х+у«т сагир!ех (2 = х( (2 2+= е( ) // получает доступ к представлению через += Глава ) ) Перегрузка операций 336 11.3.2.
Смешанная арифметика. Чтобы справиться с выражением сотр!ех Н = з + Ь) нам нужно определить операцию е, работающую с операндами разных типов. В терминологии языка Роптал, нам требуется смешапнов арифметика (т)хек-тос)е аггг))те!!с). Этого можно добиться путем добавления всех нужных версий операции сложения: с1ат сотр!ех г)оиЫе ге, ип; риЫ!с: сотр1еха орега1огв= (совр!ох а) ге е= а.ге; ип е= а.)т; ге!ига *1Ыз! ) совр!еха орега1оге= (г!оиЫе а) ( ге+= а; ге!ига *1Ьа; ) ,У... совр)ех орегаюгв (сотр!ех а, совр)ех Ь) ) сагир!ех г = а; гегигп г в= Ь; У вызывает сотр!ех::орегагог+=Гсотр!ех) сотр1ех орегаюгв (совр!ех а, 4оиЫе Ь) ( сотр!ех г = а; тгигп г е= Ь; ) гу вызывает сотр!еххорегагог+ =(доиЫе) сотр1ех орегаюге (ИоиЫе а, сотр1ех Ь) ( сотр!ех г = Ь; ге!игл г е= а! ) //вызывает сотр!еххорегагог+=Гг)оиЫе) Операция сложения с числом типа г!оиЫе проще, чем сложение двух комплексных чисел, что и отражается в коде данного примера.
Операции с !!оиЫе не затрагивают мнимых частей комплексных чисел, и поэтому более эффективны. Отталкиваясь от имеющихся определений, мы можем писать, например, такой клиентский код: ЗЗ7 11.3 Тип комплексных чисел гоЫ1 (сотр1ех х, сотр(ех у) сознр!ех г1 = хеу! сагир!ех г2 = к+2! созпр1ех г3 = 2+х; ) зз вызывает ореха!ог+(сотр!ех,сотр!ех) ,(г вызывает ореха!огз.!сотр!ех,з!оиЫе! зз вызывает орега(огай!оиЫе,сотр!ех) 11.3.3. Инициализация Чтобы иметь возможность копирования и инициализации комплексных чисел вещественными скалярами,нам требуется преобразование из такого скаляра в комплексное число. Например: зУ должно означать Ь.ге=3, Ьлт=О сотр1ех Ь = 3! Конструктор, принимающий единственный аргумент, осуществляет преобразование (приведение) типа аргумента к классовому типу.
Например: с1ат сотр1ех з!оиЫе ге, йи; 1зиЫзс: сотр1ех(йоиЫег): ге(з.), зт(0) () ,(з ... сотр1ех Ь = 3; означает созпр!ех Ь = сотр1ех (3) Неявное применение определяемого пользователем приведения типов возможно лишь в случае, если оно уникально 57.4). В 511.7.! рассматривается способ определения конструктора, для которого неявный вызов запрещен. Естественно, нам обязательно нужен конструктор с двумя аргументами типа г1оиЫе, а также умолчательный конструктор для построения комплексного числа (О, О): с!вяз сотр1ех ( ЫоиЫе ге, йп! риЫзс: гетр!ох(): ге(0), !т (О) ( ) сот!ех(з!оиЫе г): ге(г), йп(0) () сотгех(аоиЫег, з!оиЫез): ге(г), йп(з) () Конструктор предписывает, как сформировать значение классового типа. Конструктор используется в случаях, когда ожидается значение такого типа и когда оно может быть создано конструктором из значений, предоставляемых инициализатором, или из присваиваемых значений, Поэтому конструктор с единственным аргументом вызывать явно не нужно.
Например, 388 Глава 11. Перегрузка операций Применяя аргументы по умолчанию, мы можем добиться некоторого сокрашения кода: с1аее сотр(ех ( дгиЫе ге, йп; риЫ!с: сотр(ех(поные « =О, доиЫе( =О): ге(г), оп (1) (1 УУ ... )' При наличии в классе явно определенных конструкторов использование списков инициализации (85.7, 84.9.5) запрещается. Например: сотр1ех г1 = (3); сотр1ех г2 = (3,4); УУ еггою у типа сотр1ех есть конструктор УУ е«го«с у типа сотр!ех есть конструктор 11.3.4.
Копирование В дополнение к определенным в классе конструкторам, класс имеет умолчательный вариант (неявно генерируемый компилятором) копирующего конструктора (810.2.5), который просто копирует все поля данных. То же самое можно явным образом определить и самим: с1ат сотр1ех ( доиые ге, ип; риЫ(с: сотр(ех (сопзг сотр!ехь с): ге (с.ге), (т (с.!т) ( ) УУ ... )' В тех случаях, когда умолчательный вариант копирующего конструктора подходит, я предпочитаю ничего самому не писать. Это лаконичнее и люди заранее понимают его работу.
Кроме того, компилятору удобно оптимизировать свое собственное творение. И, наконец, ручное кодирование простейшего копирования полей данных утомительно и чревато внесением нелепых ошибок, особенно для классов со многими полями данных (810.4.6.3). Для копирующего конструктора я использую аргумент, передаваемый по ссылке, и я просто обязан это делать, поскольку копирующий конструктор определяет, как проходит процесс копирования, в том числе аргументов этого типа. Таким образом, передача аргумента копирующему конструктору по значению сотр!ех:: сотр1ех (сотр!ех с): ге (с.
ге), (т (с.ип) ( ) УУепог приводит к бесконечной рекурсии. Для остальных же функций я использую передачу аргументов типа сотр1ех по значению, а не по ссылке. Здесь у разработчика есть выбор. С точки зрения пользователя разница между вариантами аргументов типа сотр1ех или сопи сотр1ехь невелика. Этот вопрос обсуждается далее в 8! 1.6. 11.3. Тип комплексных чисел В принципе, копирующие конструкторы используются в простых случаях инициализации вроде У создается сотр!ех(2) и им инициализируется х У создается сотр!ех(2,0! и им инициализируется у со(нр(ех х = 2( со(нр(ех у = сотр(ех (2, О) ( В то же время, вызовы копирующих конструкторов могут быть легко заменены на эквивалентные варианты со(пр(ех х(2) ( со(нр(ех у (2, О) ( ,((х инициализируется значением 2 ((х инициализируется парой чисел (2,0! 11.3.5. Конструкторы и преобразования типов Мы определили по три версии для каждой из четырех арифметических операций: сотр1ех орега(оге (сотр!ех, сотр(ех) ( совр(ех орега(ог+ (сотр1ех, доиЫе) ( сотр1ех орега(ог+ (йоиЫе, сотр1ех ); гг' ..
Такое дублирование довольно утомительно, а что утомительно, то чревато ошибками. А что, если было бы по три альтернативы для каждого аргумента каждой функции. Тогда потребовались бы три версии для каждой одноаргументной функции, девять версий каждой двухаргументной функции, двадцать семь версий для каждой трехаргументной функции и т.д.