Бьерн Страуструп. Язык программирования С++. Специальное издание (2011) (1004033), страница 74
Текст из файла (страница 74)
Функция орегагог() () может бытыполько функцией-членом. 11.10. Разыменование Операция разыменования -> перегружается в классах как унарноя и постфиксная: с1ат Ргг ( У... Х* орегагог-> () ) Объектами класса Рп можно пользоваться для доступа к членам класса Хточно так же, как обычными указателями. Например: гоЫ)'(Ргг р) ( р->т = 7г ) 77 (р.прего(ог-' ()) — >т = 7 Переход от объекта р к возвращаемому вызовом р. орегагог-> () указателю не зависит от указуемого члена щ. В этом смысле операция разыменования ->, перегружаемая с помощью функции орегагог-> (), является упорной (и постфиксной). При этом никакого нового синтаксиса не вводится, так что по-прежнему после знака операции нужно указать имя адресуемого операцией члена.
Например: гоЫ е (Ргг р ) Х*Е)=р- г Х* Е2 = р.орегасог — >(); ) У синтаксическая ошибка У ой Перегрузка операции -> используется преимущественно для построения так называемых интеллектуальных указателей (лтагг ро(пгегл), то есть классовых объектов, которые ведут себя как указатели и дополнительно выполняют некоторые действия, когда через них осуществляется доступ к целевому объекту. Например, можно определить класс Лес ргг для доступа к объектам класса Лес, хранящимся на диске.
Конструктор класса Лес ргг принимает имя, которое используется для доступа к объекту на диске, 11ес ргг::орегагог->() извлекает объект с диска 359 11,10. Разыменование с1аяя йес ри сопят сааг* Ыеп10) ег/ Вес* т саге а/Ыгевв/ // ... риЫ[с: Вес ргг(сопвг сааг* р): Ыеп1111ег(р), (п саге ай!геня [0) ( ) -1[ее р1г() ( юг[ге го ьЬа((п саге айгевн,!Йеп(0)ег) / ) Лес* орега1ог-> () )1 йес* йес ргг::орегагог-> () ( г/(/п гоге аИгеня==О) т саге аИгевв=геев [гот г[(як(Ыепат)ег) гетгп [п саге ай[геяв1 ) Вес р(г можно использовать следуюшим образом: ва исг йес ( вгг[пе пате/ //... )1 //Лес, на который указывает Рес ргг иоЫ ирааге (сопя1 спас* в) йесрггр(в) / //йеср(г дяя я р->пате="Яоясое" / //обновить в (нри нсобходиности скатать с диска) // ...
) В реальной жизни Лес ргг был бы шаблоном, а тип Лес был бы его параметром. Кроме того, реальная программа обязана обрабатывать ошибочные ситуации, а ее взаимодействие с диском было бы не столь наивным, как в нашем учебном примере. Для обычных указателей языка С++ применение операции -> синонимнчно комбинации операций . и *. Пустьдля типа Уоперации ->...
* и [] имеютстандартный смысл, и указатель р имеет тип У". Тогда справедливы следуюшие равенства: // истина // истина // истина р >гп== (*о) .пю ( р) .т==р[О] .т р->т==р[0].т Как обычно, для пользовательских операций таких автоматических гарантий нет. Необходимую эквивалентность операций нужно программировать самому: с[иве Р/г го 1' ( уьр в оперативную память, а деструктор записывает измененное значение объекта на диск: 360 Глава 11. Перегрузка операций риЫ1с: У* орега!ог — > () (ге!ига р; ) Уа орегагог* () (ге!иго *р; ) УЬ орега)ог[) (1п11) (ге!иго р[1); ) ): Действительно, если вы предоставляете для пользовательского типа более одной подобной операции, будет разумно обеспечить ожидаемую пользователями эквивалентность точно так же, как бывает полезно обеспечить эквивалентность ++х, х»=1 и х=х+1 для объекта х класса, перегружающего операции +, +=, = и +.
Перегрузка операции -> представляет интерес для целого круга задач. Дело в том, что косвенные обращения (!пд!гесг!оп) составляют важную теоретическую концепцию, а в программировании на С++ операция -> реализует эту концепцию самым прямым и эффективным образом. Итераторы (глава )9) служат еше одним примером реализации концепции косвенных обращений. Еше один взгляд на перегрузку операции -> состоит в том, что с ее помощью реализуется одна из форм концепции делегирования (де!еяа!(оп; э24рьб).
Операция -> может перегружаться только в виде функции-члена. Типом воэвращаелгого ею значения может быть либо указатель, либо объект класса, перегруэкающего операцию ->. 11.11. Инкремент и декремент Если определены «интеллектуальные указатели», то стоит дополнить их перегрузкой сопутствующих традиционным указателям операций +» (инкремент) и-- (декремент). Особенно это важно в случаях, когда интеллектуальные указатели предназначаются для замены указателей встроенных.
К примеру, рассмотрим следующую традиционную программу со свойственными ей недостатками: гоЫ)7 (Т а) ( тг[гоо) э Т* р = ьг[О) ) Р *р=а; // Оорэ: р вне корректного диапазона (и это не перехватывается) // о/с Для преодоления проблем с традиционными указателями мы могли бы заменить указатель р объектом класса Ргг го Т, разыменовывать который можно лишь в тех случаях, когда он на самом деле указывает на целевой объект. Также было бы неплохо, если операции инкремента и декремента можно было применять к нему только тогда, когда указуемый объект находится в составе массива объектов. То есть нам требуется что-то вроде следующего: с!авэ Ргг го Т 11.11. Инкремент и декремент 361 юЫ)2(Та) ( т я[200] ( Р(г (о Т р < ь г < 0], г, 200) ( р ( "р с = а( /У ошибка времени выполнения <гип-(<те еггог)с р вне диапазона «»р' "р = а; //о/с ) с<авз Р(г (о Т ( т р( Т* а(тау; йи Ызе( риЫ1с с Р(г (о Т(Т* р, Т* г, <п(з); //привязка к массиву я размера (и нач.
значение р Р(г (о Т(Т" р); //привязка к об»впту; начальное значение р // префиксный // постфиксный Р(г (о Ть орега(ог«» () ( Р(г (о Торега(ог««(1н() ( // префиксный // постфиксный Р(г (о Ть орега(ог--() ( Р(г ш Торега(ог — (ш() ( У префиксная операции Ть врага(ог* () )( Аргумент типа 1л( используется для указания на то, что функция должна вызываться для постфиксной формы операции инкремента/декремента. Причем величина аргумента не играет никакой роли; этот аргумент служит лишь тупым указанием на форму операции.
Легко запомнить, для какой именно формы операций инкремента/декремента не нужен фиктивный аргумент — он не нужен для обычной префиксной формы, как он не нужен и для остальных обычных унарных арифметических и логических операций, а нужен он лишь для «странных» постфиксных ++ и --. Приведем пример использования последнего варианта класса Р(г (о Т. го<а)3 (Та) Т г [200]; Р(г (о Т р ( ь г [ 0], г, 200) ( Р орега(ог-- (О) ( р. орега(ог* () =а; У ошибка времени выполнения <лип-(<те егюг)с р вне диапазона р. орега(ог«» (] ( р.
орега(ог* () =а; // о)с Операции инкремента и декремента уникальны в том отношении, что они могут применяться префиксно и постфиксно. Отсюда следует, что мы должны перегрузить оба варианта для класса Р(г (о Т. Например: Глава !). Перегрузка операций 362 Завершение класса Ргг гю Тоставлено в качестве самостоятельного упражнения (51!.14[19)). Его дальнейшее превращение в шаблон с генерацией исключений для сообщений об ошибках составляет задачу еше одного упражнения (514.12[2)).
Работа операций ь+ и -- над итераторами рассматривается в 519.3. «Шаблонные указатели», корректно ведущие себя по отношению к наследованию, рассматриваются в 513.6.3. 11.12. Класс строк с(алв Югг(па ( Фгисг Югер; Югер' гер; с!азл Сге/) У нредстаа1 ение (гергезенгайоп) У ссьыка на слог риы!с, сlаев Капле ( ); // длл исквючений // ... )' Как и остальные члены класса, вложенный класс (пезгеа с!азз) может быть внутри объемлющего класса лишь объявлен, а определен позже: л(гисг Ят!пд:: Югер ( сйа«* л; !пг зс/ !пг и; // указатель на элементы // кол-во символов // счетчик ссылок Югер (!пг пзе, сопл( айаг* р) п =1; зс = пзсг з = пеп спас(лг+1 1; /У плюс терминальный нуль В данном разделе представлена достаточно реалистичная версия строкового класса Яптия.
Это минимальная версия, которая меня устраивает. Она обеспечивает семантику значений, операции посимвольного чтения и записи, контролируемый и неконтролируемый доступ, потоковый ввод/вывод, операции сравнения и конкатенации. Класс хранит строку в внае массива символов с терминальным нулем и использует счетчик ссылок (ге~егепсе соил/) для минимизации копирования.
Дальнейшее усовершенствование этого класса является полезным упражнением (51!.14[7 — !2[). А затем мы можем выбросить все наши упражнения и использовать класс строк из стандартной библиотеки (глава 20). Мой почти настоящий класс Яптпя опирается на три вспомогательных класса: класс Югер, который позволяет разделять истинное представление между несколькими объектами типа Яптп», имеющими одинаковое значение; класс йапйе, объекты которого генерируются в виде исключений с сообщениями о выходе из допустимого диапазона; класс Сге/ — помогающий в реализации операции индексирования, различающей чтение и запись: 11.12. Класс строк 363 вй сру (в, р); -Югер() ( ((е!е(е[] т ) Югер* ве( оии сору() ( (( (и==() ге(игп (Ь!х( ге(игп пеи Ю ер (вх,в); ) // ктоннровап(н при необходис(ое(пи ио!с( авх!ап (!п( пвх, сот( ейаг* р) ( (!'(хх! =их() ( ((е!е(е() в; т = пвх( х = пете ейаг(тв1]; ) ягсру(х,р) ( ) рг(иа(е ( Югер (еопх( Югера ) / Югера прего(ог= (сот( Югера ) // предотвращаем копирование: Класс Ю(г!пя обеспечивает обычный набор конструкторов, деструктор и операции присваивания: с1ат Ю(г(ив ( // ...
, Ю(г!ия() ( Ю(г!пл ( еоив( ейа(* ); Ю(г!пв (сопя Ю(г!пас ); Ю(г!пав орега(ог= (сот( Ю(г!пва прего(ог= (сот( -Ю(г!иа ( ); //... ) //х = "" //х = "айс" /х = о(Ьег х(гй(я ейаг*); Ю(г!пва ); Ю(ппа:: Ю(г!пя ( ) Класс Ю(г!пя реализует семантику значений. То есть после присваивания в1=х2 строки в1 и в2 абсолютно различимы и последующие изменения одной из них никак не затрагивают другую строку.