Бьерн Страуструп. Язык программирования С++. Специальное издание (2011) (1004033), страница 70
Текст из файла (страница 70)
Часто, все эти варианты похожи друг на друга — они сначала приводят аргументы к общему типу, а потом выполняют один и тот же стандартный алгоритм. Альтернатива такому множественному определению функций для каждой из возможных комбинаций типов аргументов может базироваться на автоматических преобразованиях аргументов. Например, наш класс сотр(ех имеет конструктор, который преобразовывает ((оид(е в сотр1ех. В результате мы можем определить единственную версию операции сравнения: Для арифметических типов я предпочитаю вариант с использованием =.
Можно ограничить набор допустимых для стиля инициализации со знаком = значений по сравнению с вариантом инициализации, использующим (), сделав копирующий конструктор закрытым (011.2.2) или объявив его с модификатором ехр11с1((011.7.1). Аналогично инициализации, присваивание по умолчанию одного объекта класса другому эквивалентно почленному присваиванию (010.2.5). Мы могли бы и явным образом задать такое действие с помощью сотр1ех:: орега(ог= () . Однако для простых типов, таких как сотр(ех, в этом нет никакой нужды.
Достаточно умолчательного варианта. Копирующий конструктор — и умолчательный, и пользовательский — используется не только при инициализации переменных, но и при передаче аргуменпюв, возврате значений, а также обработке исключений (011.7). Семантика этих операций эквивалентна семантике инициализации (З7.1, 07.3, З!4.2.1). Глава 11.
Перегрузка операций 340 Ьоо! орегагог== (сотр!ех, сотр!ех ); го!а)(сотр!ехх, сотр!еху) ( х==у; х==З; 3==у; ) // означает орега)ог= =(х,у) /У означает орега)ог==(хсотр(ех(3)) У означает орега)ог==(сотр!ех(3) у) Конечно, могут существовать причины для предпочтения варианта с несколькими отдельными функциями. Например, в каких-то случаях преобразования типов аргументов могут оказаться обременительными с точки зрения производительности, а в других случаях — может применяться более простой и эффективный алгоритм для специфических аргументов. Во всех остальных случаях целесообразно предоставить один наиболее обший вариант функции, базирующийся на преобразованиях типов аргументов, плюс, возможно, несколько критических вариантов, и это предотвращает комбинаторный взрыв вариантов, типичный для смешанной арифметики.
При наличии нескольких вариантов функций (операций) компилятор должен выбрать наиболее подходяший вариант, основываясь на типах аргументов (операндов) и доступных преобразованиях (стандартных или определяемых пользователем). Если невозможно выбрать единственный наилучший вариант, то выражение трактуется как ошибочное (неоднозначное) (см. В7.4). Объект в выражении, созданный явно или неявно конструктором, является автоматическим и будет уничтожен при первой же возможности (см. 510.4.10). Пользовательские преобразования не применяются неявным образом к левым операндам операций .
или ->. Это справедливо и для случаев, когда операция . подразумевается (хотя явно и не пишется): го)а е (сотр1ех х) ( 3+с) З.орегагог+= (г) ' 3+=с) У о)с сотр1ех(3) +х //еггок 3 не является обьектом класса // еггог: 3 не является объектом класса Таким образом, если операция в качестве левого операнда требует 1ча!це, то ее нужно перегрузить в классе с помощью функции-члена. 11.3.6. Литералы Невозможно определить литералы пользовательских типов в том же самом смысле, в каком 1.
2 или 12еЗ являются литералами типа йоиЫе. Однако вместо них можно использовать литералы встроенных типов (54.1.1), если в классе имеются функции-члены для их интерпретации. Конструктор с единственным аргументом служит основным механизмом реализации этого подхода. Когда такие конструкторы просты и поддаются встраиванию, естественно рассматривать вызов конструкторов с литеральными аргументами в качестве литерала. Например, я рассматриваю сотр!ех (3) как литерал типа сотр!ех, хотя технически это не так.
3Я1 11.3. Тип комплексных чисел 11.3.7. Дополнительные функции-члены До сих пор мы снабдили класс сотр1ех лишь конструкторами и арифметическими операциями. Для практической работы этого не достаточно. В частности, требуются функции, позволяющие читать вещественную и мнимую части комплексного числа: сгат сотр1ех ( ФоиЫе ге, Ьп; риЬЛс: (гоиЫе (еа1() соле( (гетгп ге; ) ИоиЫе 1таа () сопл ( ге(игп (т( ) (У ... тйпе Ьоо( орега(ог== (сотр1ех а, сотр1ех Ь) ге(игла.геа!() ==Ь.геаг() ьь а.(тае() ==Ь.ипаа() ) Часто требуется именно читать вещественную и мнимую части комплексных чисел; перезапись этих значений требуется значительно реже. Если же частичная модификация все же нужна, то можно поступить следующим образом: го(а1 (сотргехь с, ОоиЫе И) У...
е = сотр1ех (г . геа1 ( ), И); ) У присвоить здт значение Ы Оптимизирующий компилятор может здесь сгенерировать единственную операцию присваивания. 11.3.8. Функции поддержки ((зе!рег 1ыпс1)опв) Если собрать все фрагменты вместе, то наш класс сотр1ех примет внд: с1ат сотр1ех ( ИоиЫе ге, 1т( риЫ!с: сотр(ех(ИоиЫе г=О, ((оиЫе ( =О): ге(г), !т(() () ОоиЫе геа!() соли( (ге(игп ге; ) ОоиЫе!тае () сопл (ге(игл ил( ) Поскольку геа1() и !тая() не модифицируют значения комплексного числа, то естественно обьявнть их константными. Остальные полезные операции можно реализовать, отталкиваясь от геа!() и !таа(), то есть не предоставляя им непосредственного доступа к внутреннему представлению класса сотр1ех.
Например: Глава 11. Перегрузка операций 342 сотр1еха орегагог-' = (совр(ех); сотрйха орега!огв= (йоиЫе) г // — =, *=, апй /= )1 Дополнительно представляем набор глобально определяемых (не в качестве членов класса сотр1ех) функций поддержки (Ье(рег/ипс!Голл): сотр1ех орега!ог+ ( совр(ех, сотр1ех); сотр(ех орегагог" ( сотр(ех, доиЫе); сотрйх орегагог" (йоиЫе, сотр(ех); // —, *, апг(/ //упорный минус //упорный плюс сотр1ех орега!ог- ( сотр1ех) сотр(ех орега!огв (сотр(ех) Ьоо( орегагог== (сотр1ех, сотр(ех); Ьоо( орега!ог! = (сотр1ех, сотр1ех); йиеата орега!ог» (й!геата, сотр!еха); //ввод ов!свата орегагог« (ов1геат ь, сотр1ех); // вывод Отметим, что функции-члены геа1() и !тая() важны для реализации операций сравнения.
Они также важны и для иных функций поддержки. Например, полезно определить функции для работы в полярных координатах: сотр(ех ро1аг (аоиЫе гло, йоиЫе где!а ) согпр1ех сои! (сотр1ех); йоиЫе аЬв (сотр(ех); ФоиЫе агу (сотр(ех); йоиЫе погт (сотр1ех); доиЫе геа1 (сотр(ех); доиЫе 1таа (сотр1ех); // для удобство записи // для удобства записи И, наконец, нужно реализовать подходящий набор стандартных математических функций: сагир!ех асов (ветр!ел); сотр1ех аии (сотр!ех); сотрйх а!ап (сотр!ех); //...
11.4. Операции приведения типов Применение конструкторов для приведения типов удобно, но не является панацеей. Конструктор не может задаватгы 1. Неявное приведение пользовательского типа во встроенный тип (так как встроенный тип не является классом). С точки зрения пользователя представленный здесь комплексный тип почти идентичен представленному в файле <сотр!ех> типу сотр1ех<аоиЫе> стандартной библиотеки (~22.5). 11.4.
Операции приведения типов 343 2. Преобразование из нового класса в ранее определенный класс (без модификации старого класса). Перечисленные проблемы решаются при помощи явно программируемых в классе операций приведения типов (сопиегз(оп орега(огз). Функция-член хпорегаеог Т(), где Тесть имя типа, определяет приведение типа Х в тнп Т. Например, пусть определен тип «шестибитовое неотрицательное целое» вЂ” Тлу, который можно смешивать с обычными целыми в арифметических операциях: с!агз Т(пу ( сйаг г; го(йазяеп(ии() ( Я(ь-077) зйгоы Вад галие(); »=Т ) ри611с: с1авв Вай галле( ); Т(пу ()лс г) ( азяяп (1); ) Т(пуз орегагог= ((п(1) (ая(ел (1); ге!игл*зй(з( ) орега(ог тз() сопзг (гегигл г; ) )' /7 операция приведения к типу (п( диапазон допустимых значений лля типа Чту проверяется как при его инициализации целыми, так и в присваиваниях ему целых значений.
При копировании Тту проверка диапазона не нужна, и поэтому мы удовлетворяется умолчательными вариантами копирующего конструктора и операции присваивания. Чтобы обеспечить возможность обычных вычислений с переменными типа Т1пу, мы определяем операцию приведения от Тлу к 1лз: Тпу:: орегагог (лг () . Обратите внимание на то, что тип, к которому выполняется преобразование (приведение), присутствует в имени функции-операции и не может указываться в качестве возвращаемого значения: Т(пу::орегасог (п1() сопл( (гетигп и; ) //правильно (пз Тту::орегазог (пз() сопя (ге(игл г; ) //отибка В этом отношении операции приведения типов имеют сходство с конструкторами.
Теперь, если переменные типа Тзлу присутствуют там, где нужны переменные типа 1лг, необходимое преобразование от Тлу к 1лг выполняется неявным образом: /У сЗ = 60 /У проверка диапазона не выполняется (нет необходимости) //1 = 64 // выход за У(= — 4 У выход за // проверка с1 = с1+с2; 1 = сЗ-64; с2 = сз-64; сЗ = с4; 1пг зла(л () ( и у с1 = 2; Т(пу с2 = 62; Т)пу сЗ = с2-с1; Т(пу с4 = сЗ; 1пз = с1+с2; пределы диапазона: с1 не может равняться 64 пределы диапазона: с2 не может равняться — 4 диапазона не выполняется (нет необходимости) Глава 11. Перегрузка операций 344 Операции приведения особо полезны там, где чтение (реализуемое операцией приведения) тривиально, а инициализация и присваивание существенно менее тривиальны.
Классы (вггеат и озггеат полагаются на операции приведения, чтобы сделать осмысленными операторы вроде следующего: иЛИе(с(п»к) сонг«х; Операция ввода с!п»х возвращает (взуеать. Последнее неявно преобразуется к значению, отражающему состояние потока с(п. Полученное таким образом значение может использоваться в цикле гей)1е (521.3.3). Однако в общем случае, идея реализации операций приведения типа, в которых происходит потеря информации, не слишком хороша. Как правило, лучше не переусердствовать с операциями приведения типов. При избыточном определении возможны неоднозначности.