Д. Вандевурд, Н.М. Джосаттис - Шаблоны C++. Справочник разработчика (2003) (1160769), страница 75
Текст из файла (страница 75)
Однако при этом имеются определенные ограничения: захватывающая стратегия счетчика не в состоянии работать с таким преобразованием в силу того, что тип чо1<1 не содержит счетчика. Точно так же может оказаться несовместимым с захватывающей стратегией и базовый класс указываемого объекта. Глава 20. Интеллектуальные указатели 4(2 Однако такое неявное преобразование можно добавить к рассматриваемому шаблону Соипе1пдрег.
Если будет предпринята попытка преобразования, не совместимого с данной стратегией счетчика, произойдет ошибка инстанцирования. Ниже приведен пример неявного преобразования. Гепр1аее<еурепаще Т, гурепалзе СоипсегРо11су = Я1тр1еКейегепсеСоипс, Сурепаще ОЬЭессро11су = ЯсапйагйОЬзесгро11су> с1азв Соипг1пдРСг: рг1часе Соипсегро11су, рг1чаге ОЬЗесГРо11су ( рг1уаге: // Сокращения: сурейей Соип~егро11су СР> Еурейей ОЬЗе~сро11су ОР; риЬ11с: // Добавляем преобразующий конструктор и обеспечиваем // возможность доступа к закрытым компонентам другого // экземпляра: йг1епй Гетр1аке<еурепаще Т2, Гурепате СР2, гуревиче ОР2> с1азз Соипе1пдРГг; Еещр1аге<сурепаще Я> // Я может быть чо1й // или базовым классом Т Соипг1пдРсг(Соипс1пдРгг<Я, ОР, СР> сопзсй ср) ОР((ОР сопзсй)ср) СР[(СР сопзеа)ср) оЬзесс ро1пеей ео(ср.оЬзесг ро1псей ео) ( 11 (ср.оЬЭесг ро1псей Го )= ]Ч(ЛЬ) ( СР::1псгещепп(ср.оЬзесг ро1ппей Го]; Заметим, что в данном случае преобразующий конструктор более легко обеспечивает требующееся неявное преобразование типов, чем оператор преобразования (в частности необходимо убедиться в корректном копировании счетчика ссылок).
Преобразование в тип Ьоо1 может показаться очень простым: нужно лишь добавить к Соипс1пдрсг пользовательский оператор преобразованю~ типа. сещр1аее<гурепате Т, гурепаще СоипгегРо11су = Я1щр1еКейегепсеСоипс, сурепазае ОЬбесеро11су = яеапйагйОЬЗесеРо11су> с1азз Соипг1пдргг: рг1чаее Соипгегро11су, рг1уасе ОЬзесгро11су ( 20.2. Счетчики ссылок 413 риЬ11с: орегагог Ьоо1() сопле ( гесигп ГЬйв->оЬЗесс ройпсес) Го != (Т*)0; ); Данный способ работает, но допускает неожиданные и нежелательные эффекты. Например, при использовании такого преобразования можно сложить два объекта Соипкйпдрег! Это достаточно серьезно, чтобы отказаться от приведенного оператора.
Преобразование в тип Ьоо1 нужно для поддержки конструкций вида Тб (ср)... или мЬ11е (! ср).... Таким образом„разрешить данную проблему можно путем использования преобразования в чо1с)* (тип, который неявно преобразуется в Ьсо1 только там, где это необходимо, причем преобразование выполняется корректно, без побочных эффектов)~. Такой подход имеет свои недостатки, но они распространяются на те интеллектуальные указатели, для которых, как мы уже решили, неявное преобразованием в чойс) * не должно применяться. Простое (но часто не замечаемое) решение этой задачи состоит в определении преобразования в тип указателя на член вместо указателя на встроенный тип. Действительно„ тип указателя на член также поддерживает неявное преобразование в Ьоо1, но, в отличие от обычного указателя, этот тип не может использоваться с оператором с)е1еге или в арифметике указателей.
Ниже показано, как можно применить описанный метод. Севр1асе<курепаше Т, Гурепаше Соипсегро11су = Я1шр1епебегепсеСоипк, Гурепаше ОЬзессро11су = Якапс)агйОЬЗессро11су> с1авв Соипкйпдркг: рг1часе Соипсегро11су, ргйчасе ОЬЗескро11су ( рг1чаге: с1авв Воо1СопчегвйопЯиррогг ( 1пг с)илвпу> риЬ1э.с: орегасог Воо1СопчегвйопЯиррогк:: * () сипак ( гесигп ГЬйв->оЬзесг ройпгей Го 7 йВоо1СопчегвйопЯиррогс::с)плаву Оу Заметим, что данное решение не увеличивает размер СоипсйпдРГг, поскольку при этом нет добавления новых данных-членов. Использование закрытого вложенного класса позволяет избежать конфликтов с клиентским кодом.
4 Например, это сделано в классах стандартных потоков С++. Глава 20. Интеллектуальные указатели 414 20.2.9. Сравнения И в завершение рассмотрим разработку различных операторов сравнения для указа) телей со счетчиками ссылок. Встроенные указатели поддерживают как операторы проверки на равенство (= = и 1 =), так и операторы упорядочения («, = и т.д.). Для встроенных указателей операторы упорядочения гарантированно работают только для двух указателей. которые указывают на элементы одного и того же массива, но для указателей со счетчиками ссылок зто условие обычно не выполняется. Следовательно, рассматривать данные операторы смысла не имеет. (Однако при необходимости эти операторы могут быть реализованы практически так же, как и операторы проверки на равенство.) Ниже приведена реализация оператора == (реализация оператора 1 = аналогична).
Гезар1аге<гурепазаа Т, сурепке) СоипеегРо11су = Б1шр1ейебегепсеСоипс, Сурепазае ОЬбесСРо11су = Веапс)агс)ОЬбесСРо11су> с1авв СоцпейпдРГг: ргйчасе СоипсегРо11су, ргйчасе ОЬЭесГРо11су ( риЬ11с: бг1епс) Ьоо1 орегасог ==(СоппШпдРгг<Т,СР,ОР> соплей ср, Т сопле* р) гесигп ср == р; бгйепс) Ьоо1 орегасог ==(Т сопит* р, СоипкйпдРсг<Т,СР,ОР> сопвсй ср] гегпгп р == срк Гетр1асе<гурепаше Т1, Гурепазае Т2, сурепазае СР, Сурепаве ОР> 1п11пе Ьоо1 орегагог ==(Соппг1пдРгг<Т,СР,ОР> сопвсй ср1, Соппг1пдРГг<Т,СР,ОР> соплей ср2) гесигп ср1.орегагог->() == ср2.орегасог->!) ) Оператор вне класса представляет собой шаблон, который позволяет сравнивать указатели со счетчиками, указываюШие на различные типы. Его реализация демонстриру~~ возможность выделения встроенного указателя, инкапсулированного в соипсйпдрсг.
Явный вызов орегасог-> достаточно необычен, чтобы обратить внимание иа потенциальную небезопасность, заключенную в реализации данного оператор~. 20.3. Заключение 415 Два других оператора являются нешаблонными. Однако поскольку они зависят от параметров шаблонов, то должны быть реализованы как дружественные с определением внутри класса. Так как эти операторы не являются шаблонами, к их аргументам применимо обычное неявное преобразование типов, которое включает неявное преобразование нуля в значение нулевого указателя.
20.3. Заключение Шаблоны интеллектуальных указателей представляют собой, вероятно, второе по важности после контейнеров применение шаблонов. Однако детали такого применения далеки от очевидности, как наглядно продемонстрировано в данной главе. Более подробно этот вопрос рассматривается в книгах других авторов, например 11, 24]. Стандартная библиотека С++ содержит шаблон интеллектуального указателя аалто рет, предназначенного для тех же целей, что и рассмотренная в данной главе пара шаблонов Но1с1ек/Ткц1е, но без использования дополнительного шаблона путем применения правил перегрузки С++ в контексте инициализации переменных .
5 Были и другие шаблоны интеллектуальных указателей, которые предлагалось включить в стандартную библиотеку, однако они были отклонены комитетом по стандартизации С++. В библиотеке Воом можно найти ряд интеллектуальных указателей, предназначенных для решения разнообразных задач [7]. 5 Пояснение лянного механизма выходит эа рамки тематики книги, поэтому эяннтсрссонанный читатель может обРатиться к дополнительной литературе, например [161. Заметим, что шаблон аисо рек ссноьжн, в частности, на механизме, который ряд авторов считают дефектом стандарта С++.
Глава 21 Кортежи В этой книге для иллюстрации возможностей шаблонов часто используются однородные контейнеры Н типы, подобные массивам. Такие однородные структуры расширяют концепцию массива в языках программирования С и С++ и часто используются во многих приложениях. В этих языках также есть неоднородные образования, способные хранить данные — классы или структуры. Кортежи — зто шаблоны классов, позволяющие объединять объекты разных типов. Начнем рассмотрение с дуэтов (йю) — конструкций, аналогичных стандартному шаблону вес)::ра1г. Здесь будет также показано, каким образом дуэты могут быть вложены друг в друга, собирая воедино произвольное количество членов: формируя тройки, четверки и т.д.
21.1. Класс Рио Дуэт — это тип, объединяющий два объекта. Этот тип подобен классу всг):: райт, входящему в состав стандартной библиотеки. Однако в рассматриваемый здесь класс добавлены новые функциональные возможности, несколько отличающиеся от возможностей стандартного класса, поэтому и имя для него выбрано другое. В самом простом виле конструкцию ))цо можно определить так: Сешр1аее <Сурепеше Т1, Сурепаше Т2> вскцос рис ( Т1 ч1; // Значение первого поля Т2 ч2; // Значение второго поля ); Такая структура может пригодиться, например, в качестве типа, возвращаемого функцией, результаты работы которой могут быль как корректными, так и неверными.
()ио<Ьоо1,Х> генц1с = боо(); 1б (геви1с.ч1) ( ! Количество собранных воедино элементов ие может быль полностью произвольным, поскольку максимапьная глубина последовательных вложений шаблонов ограничена и зависит ог реализации компилятора С++. Глава 21. Кортежи 418 // Результат корректный; // его значение содержится в геви1е.ч2. Возможны и другие применения структуры ))по. Структура, определенная в виде шаблона, обладает некоторым преимуществом перед обычной, но это преимущество незначительное. В конце концов, не так уж сложно задать структуру с двумя полями, что позволит выбирать для этих полей осмысленные имена, Однако продолжим расширять нашу базовую конструкцию, пытаясь сделать ее более удобной в применении.
Для начала добавим в нее конструкторы. Сетр1аге <Сурепке Т1, Сурепаше Т2> с1авв (Зио ( риЬ11с: Т1 ч1; // Значение первого поля Т2 ч2; // Значение второго поля // Конструкторы. )Зио () : ч1(), ч2() ( ) (Зио (Т1 сопвгй а, Т2 сопвсй Ы ч1(а), ч2(Ы ( ) Обратите внимание, что для конструктора по умолчанию используется список инициализации, поэтому переменные-члены встроенных типов инициализируются нулями (см. раздел 5.5, стр. 78). Чтобы избежать необходимости явного указания типов параметров, можно добавить функцию, обеспечивающую вывод типов полей.
Сетр1асе <куренное Т1, Сурепаше Т2> йп11пе Пио<Т1,Т2> ваКе с)ио (Т1 сопвгй а, Т2 сопвсй Ы ( геспгп Пио<Т1,Т2>(а,Ы; ) В результате создание и инициализация объектов класса 1)ио упрощается. Вместо после- довательности инструкций )Эио<Ьоо1, Тпг> гевп1Ы геви1е.ч1 = где; геви1е.ч2 = 42> гесигп геви1с; можно написать гегпгп таКе с)ио(егие,42) 21.1.
Класс Рос 419 Хорошие компиляторы С++ способны выполнить такую оптимизацию этой инструкции, что сгенерированный код будет эквивалентен гесигп )Эио<Ьоо1,1пг>(сгие,42) Гезпр1асе <Гурепазпе Т1, с1авв Рис ( риЬ11с: сурет)ей Т1 Туре1; курейшей Т2 Туре2; епш» ( Ы = 2 ); Сурепаше Т2> // Тип первого поля // Тип второго поля // Количество полей Т1 ч1; Т2 ч2; // Значение первого поля // Значение второго поля // Конструкторы ))ио (): ч1(), ч2() ( Био (Т1 сопвсй а, Т2 сопвсй Ь) ч1(а), ч2(Ь) ( ) ): На данном этапе разработки шаблон очень похож на реапизацию шаблона вес):: райг. Однако между этими шаблонами есп ряд различий: ° разные имена; ° в шаблон (зло добавлен член )ч, обозначаюший количество полей; ° в нашем шаблоне нет инициализации его членов, позволяюшей выполнять неявное преобразование типов в процессе конструирования объекта; ° в шаблоне )Эио отсутствуют операторы сравнения.