Бьерн Страуструп. Язык программирования С++. Специальное издание (2011) (1004033), страница 71
Текст из файла (страница 71)
Подобные неоднозначности выявляются компилятором, но от них бывает трудно избавиться. Вероятно, лучше сначала попробовать ограничиться именованными функциями, например Х::тайе )пг() . Когда такая функция становится слишком популярной, в результате чего код теряет элегантность, тогда и стоит подумать о введении операции приведения Х:: орезагог зпг() . Когда же одновременно определяются и пользовательские операции, например ь, и пользовательские приведения типа, то могут возникать неоднозначности следующего вида: (пг орегагог+ ( Тзпу, Тгпу); кой(у(Тту б (пг() н(; г? еггог, неоднозначность: орегагогь(г, тгпу(()) или гпг(г)+(? ) Тогда лучше ограничиться чем-либо одним — либо пользовательской операцией сложения, либо пользовательским приведением от Ппу к (пг.
11.4.1. Неоднозначности Присваивание значения типа Гобъекту класса Хдопускается в тех случаях, когда определена операция присваивания Х:: орегагог= (У), где Весть У, либо имеется уникальное преобразование от Кк Е Трактовка инициализации абсолютно аналогична. В ряде случаев значение требуемого типа может быть создано путем многократного применения конструкторов и операций приведения. Это нужно осуществлять при помощи явных преобразований; неявное применение пользовательских преобразований типов осуществляется лишь однократно. Иногда значение желаемого типа может быть создано более, чем одним способом — такое допустимо.
Например: с!азз Х ( )*... *) Х(упе) г Х(сваг*) г с!аев у ( ) *... *) у((пг); ); с(аее У ( ) *... *) У (Х); ); ) 1.4. Операции приведения типов 345 Х1'(Х)з у)'(у) ! Уа(У) ! я(Х(")хос") ) ) К(Я(ьЯиху") ): ) Пользовательские преобразования типов рассматриваются лишь тогда, когда они необходимы для разрешения неоднозначности вызова. Например: с1аезХХ ( /*... */ ЛХ(1из) ! ); го1й й (йоиЫе) го11й (ХХ); ыйг() й(1) ) // ЫйоиЫе(!)) или й(ХХ(!))? й(йоиЫе(!))! Вызов (з(1) трактуется как 1((йоиЫе(1) ), ибо здесь используется стандартное, а не пользовательское преобразование типа (я7.4).
Правила для преобразований и не слишком просты, и весьма сложны в документировании, и не столь общие, как можно было бы подумать. Однако их применение безопасно, а результаты не слишком непредсказуемы. Лучше применить явное разрешение неоднозначности, чем искать ошибку, вызванную неожиданным (неявным) преобразованием. Требование строгого анализа снизу-вверх предполагает, что тип возвращаемого значения не участвует в разрешении перегрузки. Например: ()иай орегагогл ((йиай, (1иай) ио(й ((йоиЫе а1, йоиЫе а2) ( (йиай г1 = а1ла2; // сулитрование с двойной точностью Диай г2 = Диай(а)) ла2/ //форсируем акай-арифметику ио!й И () ( 1'(1) ' 1'(Х(1) ); )(У(1) ); а ( "Мас!т" ) з с(азз 9лай риЫ(с: Диай(йоиЫе) ) // ... )! // еггог: неоднозначностнс )(Х(1)) или)(У(1))? // о/~ // ОА.
// епох:лужки два пользовательских преобразования; // я(У(Х("Мас)) "))) по( (г(ей // о!с' К(У(Х('Юос'))) //о/: к(г(Х(З у ))) Глава 11. Перегрузка операций 346 Причинами такого подхода являются соображения о том, что анализ снизу вверх более понятен, и что не дело компилятора решать за программиста, какая нужна точность для выполнения сложения. Когда типы операндов инициализации или присваивания определены, они оба используются для разрешения неоднозначностей. Например: с1аее Юеа( ( риЫ(с: орегагог ИоиЫе ( ); орегагог ии ( ); //„. )' гоЫе(Леа! а) ( цоиЫе И = а; йит= а; 0=а; //Ы = а.НоиЫеО( //1 = апы(); //Ы = а,НоиЫеО( //1 = а.(о(О; 1=а; И здесь анализ типов идет снизу-вверх: в каждом отдельном случае лишь тип операции и типы операндов используются для разрешения неоднозначности. 11.5.
Друзья класса с!аее Магпх; с1аее Уесгог ( Яоагг(4); // ... /пеиИ Уесгог орегагог* (сове( Магпха, соле( Уесгога ) Обычное объявление функции-члена имеет три логически разных последствия: 1. Функция имеет доступ к закрытой части класса. 2. Функция находится в области видимости класса. 3. Функция вызывается для объекта класса (получает указатель 141е). Приписывая функции-члену модификатор Майе (В10.2.4), мы обеспечиваем для нее первые два свойства. А если мы припишем функции модификаторуг(еиФ, то наделим ее одним лишь первым свойством.
Например, можно было бы определить операцию умножения объектов типа Маитх и Уесгог. Каждый из этих типов скрывает внутреннее представление и обеспечивает полный набор операций для манипулирования своими объектами. Наша операция умножения не может быть функцией-членом обоих классов, да и вообще, вряд ли мы захотим предоставить любым пользователям низкоуровневь)е возможности читать/переписывать внутренее устройство объектов типа Маитх и Уесгог. Во избежание этого мы объявим операцию умножения другом обоих классов: 347 11.5. Друзья класса е(аее Ма(пх ( Уес(ог г [4); Д...
Гпепй Уее(ог орега1ог* (еопт Ма(гс(а, еопв( Уее(ога ) Уее(ог орега(ог* (сопя( Ма(г(ха т, сопя( Уее(ого г) ( Уес(ог г; )ог((п(('= О; (<4( (ге) Дг((1 = т((] г.г[(] = О( 7ог((п(! = О( !<4( 7ее) г.г[!) е= т.г[!) .г[!) *г.г[!) ( ) ге(игп т Объявление функции другом можно помешать как в открытые, так и в закрытые секции класса — зто не имеет значения. Как и функции-члены, функции-друзья явным образом объявляются в классе, друзьями которого они являются. Так что они являются частью интерфейса класса в той же мере, что и функции-члены. Другом класса может быть объявлена и функция-член другого класса.
Например: е(аяя ЙЫ( (1ега(ог ( ,У ... (п(* пех((); )( с(ат С(л( ( уг(епй (п(* !луи ((ега(ог:: пех(( ) 1 р... )( Нередко все функции одного класса являются друзьями другого класса. Это можно объявлять короче: с(аел 1.(е( уг(еп0 с!аел й(11 ((ега(ог( р...
Такое обьявление делает все функции класса ХЫ ((ега(ог друзьями класса Ия(. Ясно, что целые классы друзьями объявляют лишь в случаях, когда имеется теснейшая концептуальная связь между ними. Однако для таких случаев су(цествует и иная альтернатива в виде определения класса внутри определения другого класса [вложенный класс — пел(е(( с(аел) [см. 524.4). Глава 11, Перегрузка операций 348 11.5.1. Поиск друзей Как и объявление функции-члена, объявление друга не вносит его имя в охватываюшую область видимости.
Например: с!ат Ма1пх ( Зпепд сйгвв Х(ест; (Нет( Маа 1х )пиесе (сопи! Магпха ); ~7... Хвост х; д'ессоп Х)огт нет в текущей области видимости Маспх ("р) (сопл(Маатха) = ь(пиесе; дегкоге (п»енОнет в текущей области видимости Для больших программ и больших классов это как раз хорошо, что классы не вносят «по-тихому» имена в окружающие области видимости, Это особенно важно для шаблонного класса, который может конкретизироваться в различных контекстах (глава 13).
Дружественный класс должен быть предварительно объявлен в непосредственно охватывающей области видимости, или же определен в той же самой области видимости, где определяется и класс, объявляющий его другом. Более отдаленные (внешние) охватывающие области видимости в расчет не принимаются. Например: с1аевАЕ( !*...*l ); йне друг класса У патеврасе Ж ( с1аев Х( !*... *l ); ,(г друг класса У с1аев У /Непд с(аев Х; Уе)спи с!аев У( Ге1епд с1аев АЕ; )' с(авв У( /*...*/ ); ) 11 друг класса У коы Г(маспха т) (пиесе (т) ( ) ~У ип етр - друг класса Ма(г)х Итак, дружественная классу функция или объявляется в непосредственно охватываюшей области видимости, или имеет аргумент типа класса (нли производного от него класса) (В(3.6), или вообше не может быть найдена.
Например: Дружественная функция может явно объявляться таким же образом, что и дружественный класс, 'или же ее можно искать по типу аргументов ЯВ.2.6) даже в случае, когда она объявлена вовсе не в самой близкой из охватываюших областей видимости. Например: ЗЯ9 11 5. Друзья класса /)() нет в этой области видимости с1авв Х )пеЫ иоЫГ() ( Яепа уоЫ й (сопи Хь); ): // бесполезно //мохсно найти по типу аргумента уоЫ я (сопле Ха х); ( )() ' й (х); ) //1() нету в области видимости //йО - друг класса Х 11.5.2.