Д. Вандевурд, Н.М. Джосаттис - Шаблоны C++. Справочник разработчика (2003) (1160769), страница 74
Текст из файла (страница 74)
Счетчики ссылок Этот шаблон не содержит ничего сложного. Самый тонкий момент — проверка на присвоение самому себе в операторе копирующего присвоения. В большинстве случаев оператор присвоения должен отсоединить указатель со счетчиком от указываемого объекта, тем самым уменьшая связанный с объектом счетчик, что может привести к удалению'самого объекта. Однако при присвоении самому себе такое действие будет преждевременным и приведет к некорректному результату. Заметим также, что необходимо использовать явную проверку на равенство указателя нулю, поскольку нулевой указатель не имеет связанного с ним счетчика. Альтернативой нашему подходу может быть делегирование проверки классам стратегий.
Кстати, одна из возможных стратегий может состоять в том, чтобы не допустить существования нулевого указателя со счетчиком. Применяя такую стратегию, вы получите в результате определенное повышение производительности. Для включения стратегий использовано наследование. Это гарантирует, что если стратегии представляют собой пустые классы, то для них не потребуется лишняя память (если компилятор способен к оптимизации пустых базовых классов, описанной в разделе 16.2, стр. 315).
Для того чтобы избежать видимости членов классов стратегий в классе интеллектуального указателя, можно использовать описанный в разделе 16.2.2, стр. 318, шаблон Вавемет)эегразг. Поскольку здесь есть больше одного аргумента шаблона по умолчанию, может оказаться, что выгоднее использовать описацную в разделе 16.1, стр. 311, методику для удобной и выборочной подмены значений йо умолчанию. Однако не будем делать этого по соображениям краткости. 20.2.5. Простой иезахватывающий счетчик Хотя конструирование СоцпстпдРГг полностью завершено, реалиэоиия конструкции все еше не закончена Здесь все еше нег кода стратегии счетчика.
Давайте начнем с того, что рассмотрим стратегию счетчика, который не хранится в указываемом обьекте, т.е. незихваюываюи)ую (или иертрушаюи(ую (пошитая(уе, поп!ппив)те)) стратегию счетчика Главным вопросом данной стратегии оказывается размещение счетчика и выделение лля него памяти. Счетчик должен совместно использоваться несколькими объектами Соипсйпдрпг, следовательно, он должен существовать до тех пор, пока не будет уничтожен последний интеллектуальный указатель.
Обычно это выполняется с помощью распределителя специального назначения, задача которого — распределение памяти для малых объектов фиксированного размера. Однако, поскольку конструирование таких распределителей — тема не совсем уместная в книге о шаблонах С++, воздержимся от обсуждения данного вопроса . Вместо этого будем считать„что у нас имеются функции и11ос соипсег ( ) и с)еа11ос соипсег ( ), задача которых — распределение памя- з Распределители могут быль иврамвтризованы различными путями (например, для выбора стратегий со ссылками лля параллельного доступа), но мы не думаем, что их рассмотрение облегчит понимание шаблонов и их применения.
Глава 20. Интеллектуальные указатели 408 ти для величин типа вйхе г. При данных предположениях можно записать наш простой счетчик, как показано ниже. вйпс1ис)е <вЫс)ей.)з> // Определение типа вйае г ()Тпс1цде "а11осасог.)трр" с1авв 81шр1еиебегепсеСоцпс ( ргйчаге: в1зе г* соцпсег; // Выделенный счетчик рц)>11с: Я1шр1еиебегепсеСоцпс () ( соцпсег = Ы(ПЬ| ) // Конструктор копирования и оператор копирующего // присвоения по умолчанию вполне работоспособны, // так как просто копируют счетчик рцЫТс: // Выделение счетчика и инициализация его значения гешр1аге<гурепаше т> чо16 Тпйг (т*) ( соцпсег = а11ос соцпсег(); *соцпгег = 1; ) // Удаление счетчика Гешр1аге<сурепате Т> чоЫ дйврове (Т*) ( деа11ос соцпгег(соцпсег); ) // Увеличение на единицу Гещр1асе<суредаше Т> чойс( 1псгешепк (Т*) ( ++*соцпгег; // Уменьшение на единицу Гешр1ате<сурепате Т> чо16 с)есгещепа (Т*) --*соцпгег; ) // Проверка на равенство нулю Гешр1асе<сурепаше Т> Ьоо1 Тв лего (Т*) гегцгп *соцпсег == 0; Поскольку эта стратегия не пустая (в ней хранится указатель на счетчик), она увеличивает размер класса Соцпсйпдрсг.
Этот размер может быть уменьшен, если хранить указатель на обьект отдельно от счетчика, вместо размещения его непосредственно в классе интеллектуального указателя. Такое решение требует внесения изменений в кон- 20.2. Счетчики ссылок струкцию стратегии и приводит к снижению производительности доступа к объекту из-за внесения дополнительного уровня косвенности.
Заметим также, что эта конкретная стратегия не использует сам указываемый объект. Другими словами, параметр, передаваемый ее функциям-членам, никогда не используется. В следующем разделе вы познакомитесь с другой стратегией, которая использует данный параметр. 20.2.6. Шаблон простого захватывающего счетчика Захваеываяииая (или разруиающая (!пчаз!че, )п)гвз1че)) стратегия счетчика представляет собой стратегию, которая размещает счетчик в типе самого обрабатываемого объекта (или, возможно, в некоторой области, управляемой данным объектом).
Это условие требует выбора соответствующей конструкции типа объекта в процессе его разработки, так что данное решение оказывается привязанным к конкретной реализации объекта. Однако в иллюстративных целях будет разработана несколько более обобщенная захватывающая стратегия. Для выбора места размещения счетчика в указываемом объекте используем параметр шаблона, не являющийся типом и представляющий собой указатель на член класса указываемого объекта. Поскольку в этом случае память для счетчика выделяется как часть памяти для самого объекта, реализация такой стратегии проще, чем незахватываюшей стратегии, хотя синтаксис указателя на член класса оказывается менее обобщенным.
// ро1псегз/щещЬегкехсоцпс.Ьрр сещр1асе< Сурепке ОЬЗесСТ, // Тип класса со счетчиком сурепаще СоцпСТ, // Тип указателя СоцпСТ ОЬзесСТ::*СоцпсР> // Размещение счетчика с1азз ИещЬегпейегепсеСоцпп рцЫ1с: // Конструктор и деструктор по умолчанию // вполне работоспособны // Инициализация счетчика единицей чоЫ 1п1С (ОЬЭесСТ* оЬ3есп) ( оЬЗесС->*СоцпСР = 1; // Никаких действий по уничтожению счетчика не требуется чоЫ с)1врозе (ОЬЗесСТ*) ( ) // Увеличение значения счетчика на единицу чоЫ 1псгещепс (ОЬзесст* оЬйесс) ( ++оЬбеск->*СоцпСР; Глава 20. Интеллектуальные указатели 410 // Уменьшение значения счетчика на единицу чо1г) с)есгешепс (ОЬЭессТ* оЬЗесс) ( ;-оЬЭесс->*СоцпСР; // Проверка значения счетчика на равенство нулю сетр1ате<сурепаше т> ьоо1 1в лего (ОЬзесст* оЬэесс) ( гегцгп оЬЗесг->*СоцпсР == 0; ) Данная стратегия по~впаяет программисту, реализующему класс, быстро снабдить его указателем со счетчи1ом ссылок.
Приведем набросок схемы такого класса. с1авв МападедТуре ( рг1часе: в1зе с ген соипгз риЬ11с: гурейе1 Соипс1пдРсг<мападег)Туре, МешЬегпейегепсеСоипс <Мападес)Туре, в1ге г, амападес)Туре::ген соцпг» При таком подходе мападес)Туре:: Ргг представляет собой удобный способ ссылки на тип интеллектуального указателя, предназначенного для доступа к объекту нападет)Туре. 20.2.7. Константность В С++ типы х сопла* и х*сопвг различны.
Первый означает, что указываемый элемент не может быть модифицирован, в то время как второй говорит о неизменности самого указателя. Такая же двойственность отмечается и у рассмотренных указателей со счетчиками ссылок: Х сопел* соответствует СоипсйпдРГг<Х сопел>, в то время как Х*сопвг соответствует Соцпс1пдРгг<Х> сопел. другими словами, констант- ность указываемого объекта представляет собой свойство аргумента шаблона. Рассмотрим, как это наблюдение влияет на открытые функции-члены Соипг1пдргг.
Операторы разыменования не изменяют указатель, поэтому они являются константными функциями-членами, обеспечивающими доступ к узвэываемому обьекту. Поскольку констаитность данного объекта определяется параметром шаблона т, в возврашаемом типе дан ных операторов можно использовать значение Т без уточняющих квалификаторов. Значение 1пс* не может быть инициализировано значением 1пг сопев*, поскольку это в конечном счете может привести к изменению константного объекта посредствои 20.2. Счетчики ссылок 411 неконстантного. Но тем же соображениям необходимо гарантировать, что соипс1пдркг<1пс> не может быть инициализировано ни значением типа Соипп1пдркг<зпс сопнс>, ни даже просто 1пс сопнк*.
И вновь использование простого (не сопи к) параметра шаблона Т влечет за собой нужный эффект. Это может показатъся достаточно простым соображением, однако весьма распространены реализации интеллектуальных указателей, которые объявляют конструктор или оператор присвоения принимающими параметр типа Т сонно* (по всей видимости, напрасно). Рассмотренные соображения применимы и к операторам присвоения. Разумеется, сами эти операторы не могут быть константными. 20.2.8. Неявные преобразования типов Встроенные указатели могут участвовать в ряде неявных преобразований типов, а именно: ° преобразовании к кго Ы*; ° преобразовании к указателю на базовый подобъект указываемого объекта; ° преобразовании к типу Ьоо1 (йа1не в случае нулевого указателя и сгце в противном случае).
Возможна ситуация, когда потребуется эмулировать такие преобразования типов в шаблоне Соцпк1пдРкг, однако это далеко не тривиальная задача, как вы вскоре увидите. Кроме того, некоторые программисты предпочитают добавлять в свои интеллектуальные указатели преобразования в тип соответствующего встроенного указателя, например чтобы Соипс1пдркг<1пп сопвс> могбыть преобразован втнп 1пк оопнп*. К сожалению, возможность неявного преобразования в типы встроенных указателей разрушает исходную посылку о том, что все указатели на объект со счетчиком ссылок являются указателями типа Соипсйпдрсг. Таким образом, приходится отказаться от предоставления такой возможности неявного преобразования типов, и Соипк1пдрсг<Х> не может быть неявно преобразован в згоЫ* или Х*.
Другими недостатками неявного преобразования в типы встроенных указателей (в предположении, что ср представляет собой указатель со счетчиком ссылок) являются следующие: ° становятся корректными как операция йе1есе ср, так и:: йе1есе ср; ° все виды некорректной арифметики указателей становятся недиагностируемыми (например, выражения ср [и), ср2-ср1 и т.п.). Тем не менее неявные преобразования в другие специализации Соипс1пдРсг могут "меть смысл. Например, можно представить неявное преобразование в Соипс1пдРКг<чо1с(> (этот тип может быть полезным типом непрозрачного указателя наподобие чо16*).