Бьерн Страуструп. Язык программирования С++. Специальное издание (2011) (1004033), страница 73
Текст из файла (страница 73)
Передача копии в в аргумент аея не составляет труда — вызов конструктора класса летпя поможет это выполнить. Получение еше одной копии при выходе из «() требует еше одного вызова ззе!пя(сопл!затаяв); на этот раз инициализируется временная переменная (310.4.10), которая затем присваивается переменной з. Часто одна из этих операций (но не обе сразу) может устраняться в процессе оптимизации. Для класса, в котором копирующий конструктор и операция присваивания не определяются явным образом, предоставляются соответствуюшие умолчательные варианты от компилятора (310.2.5). Это подразумевает, что копируюшие операции не наследуются (312.2.3).
11.7.1. Конструктор с модификатором ехр11с11 Как мы уже знаем, конструктор с одним аргументом определяет неявное приведение типа. Для некоторых типов это является идеальным решением. Например, тип сотр!ех может инициализироваться типом рп» сотр!ех е = 2; УУ инициализация з значением сотр!ел(2) зге!паз = 'а' УУ создается строка з с т(('а') элементами Это прошло бы, но вряд ли соответствовало бы настояшему намерению программиста. Неявные приведения типа можно подавить, объявив конструктор с ключевым словом ехр!!с!!. Такой конструктор можно вызывать только явно.
В частности, там, где принципиально требуется копирующий конструктор (311.3.4), конструктор с модификатором ехр!!с!г вызываться неявно уже не будет. Например; с!аев бге!пе УУ ... ехр!ю» Иге(пд (т! и ) ! Б(е(пе (сопл! сйае* р) ! УУ выделить и байт УУ инициализировать С-строкой В других случаях неявное приведение типа нежелательно и может приводить к ошибкам.
Например, если бы мы могли инициализировать затая размером (имеет тип !пг), кто-нибудь написал бы: Глава ) 1. Перегрузка операций 35Я го!4 !'(Вюг!п8) ю Яюг!п8* р1 = пеюг В!пня ("Ег!с" ) Вюг!п8* р2 = пеюг В!лая (10); геиюгп 10! Может быть разница между Ююг!п8 з1 = 'а' ю и В!пня з2 (10) В о!с 5(г(пя с местом для !О символов покажется слишком тонкой, но она более заметна в реальном коде, чем в надуманных примерах. В классе Раге мы использовали тип !и!для представления года (З)0.3). Будь наше отношение к разработке Ра!е более серьезным, мы могли бы создать класс Уеагдля обеспечения более строгой проверки во время компиляции.
Например: с)авв Уеаг ( !и! у; риЬВс: елрлсп Уеаг(тю ю): у(ю) ( ) У(создание Уеаг из юпю орегаюог !пю() санга (ге!игл у; ) Упреобразование: Уеаг в тю )ю с1агв Разе риЬВсю Раюе (юпю 4, Мопюл т, Уеагу) ю УУ ... )ю Раюе 43 (1978,!еЬ, 21); УУ ел оп 2! зто не Уеаг Раюею(4(21ДеЬ, Уеаг(1978) ) ю Уо)ю Класс Уеаг является простой «оберткой» («зггаррег») вокруг типа та Благодаря наличию определения лля орегагог юпг() тип югеаг неявно преобразуется в юпг каждый раз, когда это необходимо.
Из-за того, что мы снабдили конструктор модифи- Вюг!п8 з1 = 'а' ю Вюг!п8 з2 (10); Вюг!п8 «3 = Бюг!п8 (10) ю Вюггп8 з4 = "Впап" ю Вюг!и8 з5( "саи)юу" ) ю Вюг!п8 8 ( ) ( !'(10) ю !'(Ююг(пя (10) ) ю г" ("Агюлиг") ю г"(з!) ю IУ еп ог; неявного преобразования слог->504п8 нет УУ о!ю: 5(г!п8 с местом для ! О символов УУ о)с' 5(г!пя с местом для 10 символов УУ о!ю: з4 = бття("Впоп") /' еггог: неявного преобразования (пю->Вюг(пя нет УУ оус)(5(г(пя("Агюлююг')) В еггоп неявного преобразования !пю->Вюг!п8 нет II еггог: неявного преобразования сЬаг->5(г!пя нет 11.8. Индексирование катором ехрвс!<, мы можем быть уверены, что преобразования из <п1 в Уеаг происходят только тогда, когда мы об этом просим явно, а случайные оказии вьпавливаются компилятором. Функции-члены класса Уеаг легко превращаются во встраиваемые функции, так что нет никаких дополнительных затрат по памяти и быстродействию.
Аналогичную технику можно применить и к диапазонам (825.6.1). 11.8. Индексирование с1азз Атос ( зи ис< Ра!г з<г!пл пате; «оиЫе ча<; Ра!г(з<г!пбп=" ",«оиЫе ч =О): пате(п), ча!(ч) (1 )< чес<огкРа(г> чес < Атос(сопев Азюсй) ) I/ рггча<е - для предотвращения копирования Аззось орега<ог= (сон<а Аззосв ) ! 1! рг<ча<е — для предотвращения копирования риЫ!с: Азюс() (1 сопя< <<оиЫеь орега<ог [) (сопя< з<г!пяь) <<оиЫев орега<ог [1 (з<г!плй ) ! чо!<< Рг!и< а<! ( ) сопл<! )< Класс А<нос хранит вектор элементов тиг)а Ра(г.
Реализация использует тот же самый тривиальный и неэффективный метод поиска, что и в 85.5: й поиск з; возврат ее значения в сзучае накожг)ения; в противно.и случае У! создаем новый Ран и возвращаем )июлчатечьное значение 0 У «оиЫей Аззос<:орега<о<[) (зиэпяй з) ( <ог (чес<огкра!г> <: йега<ог р = чес. Ьея!и ();р! =чес. епд() < ++р) <! (з==р->пате) ге<игл р->ча1; и начальное значение: 0 У возврат последнего элемента Д!б.3.3! чес.ризл Ьасй (Ра1г(з, О) ) ) ге<игл чес.
Ьасй ( ) . ча1< Функцию-операцию орега<ог(1 определяют с целью придать смысл операциям индексации над объектами классов. Второй аргумент этой операции (индекс) может иметь любой тип. Это и позволяет определять такие типы стандартной библиотеки, как вектора, ассоциативные массивы и т.д. Для иллюстрации перепишем пример из 85.5, в котором ассоциативный массив использовался для подсчета количества вхождений слова в содержимое файла.
Там для этого использовалась функция. Сейчас же мы определим свой собственныи ассоциативный массив: Глава 1 1. Перегрузка операций 356 Так как класс Аввос скрывает свое внутреннее представление, то требуется открытый метод для вывода его содержимого; гоЫ Атос::рпп( аП() соле! ( аког ( гесгог<ра/г>:: сопл! Пегагог р = ге< . Ьеа/п ( ); р! г асс . елд ( ); ь ьр ) сои!«р->лате « ": " «р->во! « ' ~п'; ) Вот пример простого клиентского кода, использующего класс Аввос: тг та(л() Уподсчитать кол-во вхождений каждого слова во входном потоке ( згг(пя ЬиУ; Атос вес; нли1е(сгп»Ьи!) вес(Ьиу] ьь! вес.рнл! аП() ) Дальнейшее развитие идеи ассоциативного массива представлено в 817.4.1.
Операцию (] можно перегружать только в виде функции-члена. 11.9. Функциональный вызов соЫ пела!с (совр!ель с) (с = -с; ) гоЫ3(сев!ос<ветр(ех>в аа, 1П!<сотр!ех>в П) аког еасв (аа. Ьеягп (), аа. епд(), пеааге); )о~ еас(з (П. Ьее1п (), П. епд ( ), пеяаге) ! ) // поменят~ знак у всех эл-ов вектора П поменять знак у всех зл-ов списка 1 Также нх часто называют функторами (/йпс!огз). — Прим. ред. Вызов функции, то есть выражение вида ехргезз(оп(ехргезз(ол-йзг), интерпретируется как бинарная операция с левым операндом ехргезз(ол (имя функции или именующее выражение) и правым операндом ехргезз(ол-Пз! (список аргументов). Эту операцию можно перегружать, как и большинство других операций С++.
При этом список аргументов для функции-операции орегагог() () вычисляется и проверяется по обычным правилам передачи аргументов. Чаще всего перегрузку операции вызова функции осуществляют для классов, имеюших единственную или доминируюшую над другими операцию. Самым очевидным и, вероятно, наиболее полезным применением перегруженной операции () является предоставление обычного синтаксиса функциональных вызовов классовым объектам, которые в некотором отношении ведут себя как функции. Объекты, которые ведут себя как функции, принято называть функциональными объектами или обьектоми-функциями (~иле!/оп о1уесм) (518.4)). Объекты-функции важны, так как позволяют писать код, принимающий нетривиальные операции в качестве параметров.
Например, в стандартной библиотеке реализовано множество алгоритмов, которые вызывают функцию для каждого элемента контейнера. Рассмотрим пример: 11 9. Функциональный вызов 357 Этот код предназначен для изменения знака (на противоположный) у калщого элемента вектора и списка. А что, если нам потребуется добавить сотр1ех(2, 3) к каждому элементу? Это легко можно сделать следующим образом: юЫ а~Ы23 (ветр!ах ь с) ( с += сотр1ех (2, 3); ) гоЫе(вес!ог<сотрьгх>а аа, 1йг<сотр(ех>ь И) ( уог еасд (аа.
Ьеа1п (), аа. епП(), агЫ23) ! 3ог еасд (П. Ьеа1п ( ), П. еиП ( ), агЫ23) г ) А как написать функцию, прибавляющую к каждому элементу произвольное комплексное значение? В этом случае нам потребуется нечто, чему мы можем передать произвольное значение, и что затем будет использовать это значение при каждом вызове. Это невозможно проделать с функциями, оставаясь лишь в контексте функций. Требуется передавать наше значение в некоторый констекст, окружающий функцию.
Все зто довольно запутанно. В то же время, нетрудно написать класс, демонстрирующий указанное поведение: с1ат А~Ы ( согир1ех га1г риЬИс: А~Ы(сотр1ех с) (го!=с! ) У сохраняем значение АПП(ПоиЫе г, ПоиЫе 1) (га1=сотр(ех (г, 1); ) юЫ орега<ог() (сотр!ехь с) сопя! (с+< иа1! ) Уирибавляем значение и аргументу )' Объект класса АчЫ инициализируются произвольно задаваемым комплексным числом, а когда затем к этому объекту обращаются с помощью операции (), он прибавляет комплексное число к аргументу.
Например: гоЫ Й (гесгог<сотр!ех>ь аа, Пв!<сотр(ех>й П, сотр!ех е) ( 1ог еасЬ (аа. Ьеаги (), аа.епП(), АгЫ(2, 3) ); 1ог еась (И.Ьея!п (), П.епч!(),АгЫ(г) ) г ) Здесь комплексное значение сотр1ех(2, 3) будет добавлено к каждому элементу вектора, и комплексное число х будет добавлено к каждому элементу списка. Заметьте, что с помощью выражения АвЫ(е) конструируется объект класса АвЫ, который затем многократно используется алгоритмом 3ог еас1в ()1 АчЫ(г) не является функцией, вызываемой однажды или повторно. Повторно вызывается функция-член орегагог() () класса АсЫ. Все это работает потому еше, что3ог еасй является функциональным шаблоном, применяющим операцию () к своему третьему аргументу, даже не заботясь о том, чем он является в действительности: зьв Глава 1 1.
Перегрузка операций гетр(а<с<с(аяя 1гег, с(ат Рог> Рог)ог еасЬ (1(ег Ь, 1гег е, Рс)Я ( шЫ(е(Ы = е) )(*Ьч.ч) г ге(иго ) 7 ) На первый взгляд может показаться, что такая техника является чем-то эзотерическим, но на самом деле все это не так уж и сложно, весьма эффективно и чрезвычайно полезно (см. 83.8.5, 8! 8.4). Другими популярными задачами для применения орегагог() () являются задача выделения подстроки и задача индексирования многомерных массивов (822.4.5).