Что такое умные указатели с
Интеллектуальные указатели (современный C++)
В современном программировании на C++ Стандартная библиотека содержит смарт-указатели, которые позволяют гарантировать, что программы свободны от памяти, утечки ресурсов и являются надежными.
Использование интеллектуальных указателей
В большинстве случаев при инициализации необработанного указателя или дескриптора ресурса для указания на фактический ресурс следует сразу же передать указатель в интеллектуальный указатель. В современном C++ необработанные указатели используются только в небольших блоках кода с ограниченной областью, циклах или вспомогательных функциях, когда важна производительность и вероятность проблем с владением низкая.
В следующем примере сравниваются объявления необработанного и интеллектуального указателей.
Как показано в примере, интеллектуальный указатель — это шаблон класса, который объявляется в стеке и инициализируется с помощью необработанного указателя, указывающего на размещенный в куче объект. После инициализации интеллектуальный указатель становится владельцем необработанного указателя. Это означает, что интеллектуальный указатель отвечает за удаление памяти, заданной необработанным указателем. Деструктор интеллектуального указателя содержит вызов для удаления, и поскольку интеллектуальный указатель объявлен в стеке, его деструктор вызывается, как только интеллектуальный указатель оказывается вне области, даже если исключение создается где-либо в другой части стека.
Этот интеллектуальный указатель C++ напоминает создание объектов в таких языках, как C#: вы создаете объект, а система удаляет его в правильный момент. Отличие заключается в том, что отсутствует отдельный сборщик мусора, работающий в фоновом режиме; память управляется через стандартные правила области C++, чтобы среда выполнения функционировала быстрее и эффективнее.
Всегда создавайте интеллектуальные указатели в отдельной строке кода; ни в коем случае не делайте это в списке параметров, чтобы не произошла небольшая утечка ресурсов, связанная с определенными правилами выделения памяти спискам параметров.
В следующем примере показано, как unique_ptr тип интеллектуального указателя из стандартной библиотеки C++ можно использовать для инкапсуляции указателя на большой объект.
В этом примере показаны следующие важные шаги, необходимые для использования интеллектуальных указателей.
Объявите интеллектуальный указатель как автоматическую (локальную) переменную. (Не используйте new malloc выражение или для самого интеллектуального указателя.)
В параметре типа укажите тип, на который указывает инкапсулированный указатель.
Передайте необработанный указатель на new объект-ED в конструкторе интеллектуального указателя. (Некоторые служебные функции или конструкторы интеллектуальных указателей делают это автоматически.)
Интеллектуальный указатель удаляет объект автоматически.
Интеллектуальные указатели разработаны для обеспечения максимальной эффективности в отношении памяти и производительности. Например, единственный элемент данных в unique_ptr — это инкапсулированный указатель. Это означает, что размер unique_ptr точно такой же, как и у указателя — 4 или 8 байтов. Доступ к инкапсулированному указателю с помощью перегрузки смарт-указателя * и- > Operators значительно медленнее, чем доступ к необработанным указателям напрямую.
Интеллектуальные указатели имеют собственные функции-члены, доступ к которым осуществляется с помощью нотации «точка». Например, некоторые интеллектуальные указатели стандартной библиотеки C++ имеют функцию-член reset, которая освобождает владение указателем. Это полезно, когда нужно освободить память, принадлежащую интеллектуальному указателю, не дожидаясь, пока интеллектуальный указатель окажется вне области, как показано в следующем примере.
Смарт-указатели обычно предоставляют способ прямого доступа к необработанному указателю. Интеллектуальные указатели стандартной библиотеки C++ имеют get функцию-член для этой цели и CComPtr имеют открытый p член класса. Предоставляя прямой доступ к базовому указателю, можно использовать интеллектуальный указатель для управления памятью в своем коде и по-прежнему передавать необработанный указатель коду, который не поддерживает интеллектуальные указатели.
Виды смарт-указателей
В следующем разделе приведены различные виды интеллектуальных указателей, доступные в среде программирования Windows, и приводится описание их использования.
Интеллектуальные указатели стандартной библиотеки C++
Используйте эти интеллектуальные указатели как основной вариант для инкапсуляции указателей на простые старые объекты C++ (POCO).
интеллектуальные указатели для COM-объектов (классическое программирование Windows)
Класс Ккомхеапптр
Интеллектуальный указатель на объекты, которые используют CoTaskMemFree для освобождения памяти.
Класс Ккомгитптр
Интеллектуальный указатель для интерфейсов, получаемых из глобальной таблицы интерфейсов (GIT).
Интеллектуальные указатели ATL для объектов POCO
Помимо смарт-указателей для COM-объектов, ATL также определяет смарт-указатели и коллекции смарт-указателей для простых старых объектов C++ (POCO). в классическом Windows программировании эти типы являются полезными альтернативами для коллекций стандартной библиотеки c++, особенно если переносимость кода не требуется или если не требуется смешивать модели программирования стандартной библиотеки c++ и ATL.
Класс Чеапптр
Интеллектуальный указатель для объектов, которые выделены с помощью функции malloc C.
Smart pointers для начинающих
Эта небольшая статья в первую очередь предназначена для начинающих C++ программистов, которые либо слышали об умных указателях, но боялись их применять, либо они устали следить за new-delete.
UPD: Статья писалась, когда C++11 еще не был так популярен.
Введение
Существует техника управления ресурсами посредством локальных объектов, называемая RAII. То есть, при получении какого-либо ресурса, его инициализируют в конструкторе, а, поработав с ним в функции, корректно освобождают в деструкторе. Ресурсом может быть что угодно, к примеру файл, сетевое соединение, а в нашем случае блок памяти. Вот простейший пример:
Это удобно: по выходу из функции нам не нужно заботиться об освобождении буфера, так как для объекта screen вызовется деструктор, который в свою очередь освободит инкапсулированный в себе массив пикселей. Конечно, можно написать и так:
В принципе, никакой разницы, но представим себе такой код:
Придется в каждой ветке выхода из функции писать delete [], либо вызывать какие-либо дополнительные функции деинициализации. А если выделений памяти много, либо они происходят в разных частях функции? Уследить за всем этим будет все сложнее и сложнее. Подобная ситуация возникает, если мы в середине функции бросаем исключение: гарантируется, что объекты на стеке будут уничтожены, но с кучей проблема остается открытой.
Ок, будем использовать RAII, в конструкторах инициализировать память, в деструкторе освобождать. И пусть поля нашего класса будут указателями на участки динамической памяти:
Простейший smart pointer
boost::scoped_ptr
Он находится в библиотеке буст.
Реализация простая и понятная, практически идентичная нашей, за несколькими исключениями, одно из них: этот пойнтер не может быть скопирован (то есть у него приватный конструктор копирования и оператор присваивания). Поясню на примере:
Оно и понятно, если бы было разрешено присваивание, то и p1 и p2 будут указывать на одну и ту же область памяти. А по выходу из функции оба удалятся. Что будет? Никто не знает. Соответственно, этот пойнтер нельзя передавать и в функции.
Тогда зачем он нужен? Советую применять его как указатель-обертка для каких-либо данных, которые выделяются динамически в начале функции и удаляются в конце, чтобы избавить себя от головной боли по поводу корректной очистки ресурсов.
Подробное описание здесь.
std::auto_ptr
Чуть-чуть улучшенный вариант предыдущего, к тому же он есть в стандартной библиотеке (хотя в C++11 вроде как deprecated). У него есть оператор присваивания и конструктор-копировщик, но работают они несколько необычно.
Поясняю:
Теперь при присваивании в p2 будет лежать указатель на MyObject (который мы создавали для p1), а в p1 не будет ничего. То есть p1 теперь обнулен. Это так называемая семантика перемещения. Кстати, оператор копирования поступает таким же образом.
Зачем это нужно? Ну например у вас есть функция, которая должна создавать какой-то объект:
Это означает, что функция создает новый объект типа MyObject и отдает его вам в распоряжение. Понятней станет, если эта функция сама является членом класса (допустим Factory): вы уверены, что этот класс (Factory) не хранит в себе еще один указатель на новый объект. Объект ваш и указатель на него один.
В силу такой необычной семантики auto_ptr нельзя использовать в контейнерах STL. Но у нас есть shared_ptr.
std::shared_ptr (С++11)
Умный указатель с подсчетом ссылок. Что это значит. Это значит, что где-то есть некая переменная, которая хранит количество указателей, которые ссылаются на объект. Если эта переменная становится равной нулю, то объект уничтожается. Счетчик инкрементируется при каждом вызове либо оператора копирования либо оператора присваивания. Так же у shared_ptr есть оператор приведения к bool, что в итоге дает нам привычный синтаксис указателей, не заботясь об освобождении памяти.
Теперь и p2 и p1 указывают на один объект, а счетчик ссылок равен 2, По выходу из скоупа счетчик обнуляется, и объект уничтожается. Мы можем передавать этот указатель в функцию:
Заметьте, если вы передаете указатель по ссылке, то счетчик не будет увеличен. Вы должны быть уверены, что объект MyObject будет жив, пока будет выполняться функция test.
Итак, smart pointers это хорошо, но есть и минусы.
Во-первых это небольшой оверхед, но я думаю у вас найдется несколько тактов процессора ради такого удобства.
Во-вторых это boiler-plate, например
Это частично можно решить при помощи дефайнов, допустим:
Либо при помощи typedef.
В-третьих, существует проблема циклических ссылок. Рассматривать ее здесь не буду, чтобы не увеличивать статью. Так же остались нерассмотренными boost::weak_ptr, boost::intrusive_ptr и указатели для массивов.
Кстати, smart pointers достаточно хорошо описаны у Джеффа Элджера в книге «С++ for real programmers».
🛠 Умные указатели в C++
wcobalt
Как C++ управляет памятью?
Когда мы говорим про управление памятью в C++, мы неизменно обращаемся к термину storage duration (длительность хранения). Storage duration – это свойство объекта, которое описывает, когда тот попадает в память и когда её освобождает.
В C++ существует четыре вида [1] storage duration:
Можно сказать, что в случае с автоматической storage duration память освобождается автоматически, а в случае с динамической – вручную. Почему же тогда не использовать всегда автоматическую память?
Чтобы обойти эти ограничения, необходимо использовать динамическую память про использование которой мы и будем сегодня говорить.
Что такое умные указатели и зачем они нужны?
Используем динамическую память, отлично. Теперь объекты могут покидать область видимости, где были созданы, и иметь определяемый во время выполнения размер – жизнь стала налаживаться и жаловаться как будто не на что.
Предлагаем взглянуть на следующий фрагмент кода:
На первый взгляд, здесь всё хорошо, но есть нюансы:
При использование простых указателей (также известных как raw pointers) невозможно без дополнительных комментариев или дополнительного изучения кода определить, какой указатель объектом владеет, а какой – только использует. Взгляните на следующую декларацию:
Главная проблема здесь, что тому, кто будет вызывать функцию, совершенно неясно, должен он вызвать delete для возвращаемого указателя или за это ответственен код где-то в другой части программы. Иначе говоря, здесь не видно, является указатель владеющим или использующим.
Все вышеназванные проблемы изящно решаются умными указателями. Умные указатели в C++ – это не что-то магическое, встроенное в синтаксис языка, а не более чем набор классов из стандартной библиотеки. Разберёмся с ними один за одним.
std::unique_ptr
Первым умным указателем, с которым мы познакомимся, будет std::unique_ptr [3]. Он ссылается на объект в динамической памяти и при выходе из области видимости уничтожает хранимый объект. Взглянем на пример кода ниже:
Когда std::unique_ptr выходит из области видимости, утечки памяти не происходит, потому что в своем деструкторе умный указатель вызывает delete для объекта на который ссылается, высвобождая тем самым память.
От проблем с внезапными исключениями использующих умные указатели (в частности std::unique_ptr ) программистов защищает развёртывание стека (stack-unwinding [4]).
Подробное рассмотрение этого механизма С++ выходит за рамки статьи, но главное, что нужно знать о нём – если на стеке был создан объект, а после этого было выброшено исключение, C++ гарантированно вызовет деструктор для этого объекта. Это значит, что если мы обновим код в листинге 3 так, чтобы он использовал умные указатели, то избавимся от всех трёх вышеназванных проблем:
Здесь возможен следующий порядок вычисления аргументов:
Если при вызове new B() произойдет исключение, занятая при вызове new A() память не освободится, потому что умный указатель для этого объекта ещё не был создан, а delete никто вызывать и не собирался. Использование std::make_unique решает подобные проблемы.
std::unique_ptr используется тогда, когда объект должен иметь только одного владельца, однако мы можем передать право на владение кому-то другому. Чтобы это сделать, необходимо использовать std::move [6]. Рассмотрим код:
Когда в коде используются простые указатели и умные, сразу становится понятно, где указатель владеет объектом, а где – только использует.
std::unique_ptr – это умный указатель, о котором вы должны подумать в первую очередь, когда решите разместить что-нибудь в динамической памяти. Это ваш умный указатель по умолчанию.
std::shared_ptr и std::weak_ptr
std::unique_ptr и правда хорош, но он не поможет в ситуации, когда мы хотим, чтобы несколько объектов работали с одним общим ресурсом и чтобы в момент, когда все эти объекты были выгружены из памяти, за ненадобностью автоматически выгрузился бы и ресурс.
Вообще говоря, std::weak_ptr необходимо использовать всегда, когда надо ссылаться на управляемый std::shared_ptr объект, но не защищать его от уничтожения.
Выводы
Каждый программист на C++ должен уметь использовать умные указатели. Умные указатели – это ваш способ управления динамической памятью по умолчанию. std::unique_ptr – это ваш умный указатель по умолчанию. Использование умных указателей не противоречит использованию простых указателей, в случае, если последние используют объекты, а не владеют ими. std::auto_ptr – зло.
С++ для новичков
Программирую единорогов на C++
Введение в smart_ptr
Указатели в C и C++ дикие звери. Они чрезвычайно мощные, но в то же время такие опасные: небольшой недосмотр может нанести ущерб всему вашему приложению. Их основная проблема заключается в том, что вы и только вы должны следить за корректной работой с ними. Каждый динамически созданный объект (т.е. new T ) должен сопровождаться ручным удалением (т.е. delete T ). Забудьте об этом, и у вас получится хорошая утечка памяти.
Кроме того, динамически выделенные массивы (т.е. new T[N] ) должны быть удалены с помощью другого оператора (т.е. delete[] ). Это заставляет вас мысленно следить за тем, что вы выделили, и вызывать соответствующий оператор. Использование неправильного оператора приводит к неопределенному поведению, что ни в коем случае нельзя допускать при работе в C++.
Еще одна тонкая проблема заключается во владении. Сторонняя функция возвращает указатель: это динамически распределяемые данные? Если так, кто несет ответственность за очистку? Это невозможно понять просто посмотрев на тип возвращаемого значения.
Идея умных указателей
Умные указатели появились чтобы убрать проблемы, упомянутые выше. Они обеспечивают автоматическое управление памятью: когда умный указатель больше не используется, то память, на которую он указывает, автоматически освобождается, в отличие от традиционных указателей, которые сейчас так же известны как сырые указатели.
Умные указатели можно рассматривать как элементарный сборщик мусора: своего рода автоматическое управление памятью, при котором объект автоматически удаляется, когда он перестает использоваться программой.
Типы умных указателей в C++
C++ 11 представил три типа умных указателей, все они определены в заголовке из стандартной библиотеки:
Сейчас все выглядит запутанно, особенно непонятно какой тип умного указателя следуют использовать. Не волнуйтесь, я расскажу обо всём дальше. Давайте копать глубже!
std::unique_ptr – единоличное владение
std::unique_ptr единолично владеет объектом – никакие другие указатели не могут указывать на хранимый объект. Когда std::unique_ptr выходит из области видимости, объект удаляется. Это полезно, когда вы работаете с временным, динамически выделяемым ресурсом, который должен быть уничтожен после выхода из области видимости.
Пример использования std::unique_ptr
std::shared_ptr – разделяемое владение
Этот тип умного указателя полезен, когда вы хотите обмениваться динамическими данными так же, как вы бы это делали с сырыми указателями или ссылками.
Создание std::shared_ptr
std::shared_ptr устроен так:
Подводные камни у std::shared_ptr
У std::shared_ptr есть один неприятный сюрприз – циклическая ссылка. Если два объекта std::shared_ptr будут ссылаться друг на друга, то внутренний счётчик будет равен единице и эти объекты никогда не удалятся, например:
В коде выше создается циклическая ссылка ( эти объекты удалятся только в конце работы программы) – утечка памяти во всём её великолепии. К счастью, последний тип умного указателя придет на помощь.
std::weak_ptr – слабая ссылка
std::weak_ptr – умный указатель, который содержит “слабую” ссылку на объект, управляемый указателем std::shared_ptr. Часто используется для устранения циклических ссылок у std::shared_ptr
std::weak_ptr моделирует временное владение: когда объект должен быть доступен только если он существует и может быть удален в любой момент кем-то другим. std::weak_ptr может преобразоваться в std::shared_ptr для принятия временного владения. Если исходный std::shared_ptr будет уничтожен в процессе работы, время жизни объекта продлевается до того момента, пока не будет разрушен временный std::shared_ptr
std::weak_ptr в бою
Производительность smart_ptr
Согласно различным источникам (здесь и здесь), производительность умных указателей должна быть близка к сырым указателям. Небольшое снижение скорости может присутствовать при использовании std::shared_ptr из-за внутреннего подсчета ссылок. Но это не должно замедлять работу кода, если вы постоянно не создаете и не уничтожаете умные указатели.
При работе с памятью желательно использовать умные указатели, потому что они решают как минимум три проблемы:
Таким образом, если научиться правильно использовать умные указатели, то компилятор отловит большинство потенциальных проблем и настучит тебе по рукам 🙂
Умные указатели в C++
Автор: Igor Semenov
www.progz.ru
Источник: RSDN Magazine #1-2008
Опубликовано: 17.07.2008
Исправлено: 10.12.2016
Версия текста: 1.0
Работа с динамической памятью является одним из самых важных моментов в программировании на C++. И в то же время, эта область является источником огромного количества ошибок в программах, начиная от банальных утечек памяти и заканчивая нарушениями защиты.
Прежде чем переходить к рассмотрению реализаций умных указателей, следует обратить внимание на проблемы, связанные с использованием обычных указателей:
Рассмотрим по порядку эти недостатки, а также инструменты, позволяющие от них избавиться.
Первый недостаток состоит в том, что указатель не владеет объектом, на который ссылается. Из-за этого приходится очень внимательно относиться к уничтожению объекта, иначе возможны утечки памяти. Рассмотрим тривиальный пример:
Всё выглядит довольно надёжно: создаём объект, используем, затем уничтожаем. Но это только до тех пор, пока мы не вспомним об исключениях. Метод DoSomething вполне может сгенерировать исключение, в результате чего SomeMethod завершится, так и не освободив temp. Чтобы исключить утечку памяти, приходится писать примерно такой код:
Аналогичные нагромождения приходится возводить и в функциях с множественными точками выхода (т.е. с несколькими конструкциями return). А если объектов создаётся несколько, то код вообще разрастается до невероятных размеров.
Другой классический пример:
В этом случае, если конструктор SomeOtherClass выбросит исключение, экземпляр SomeClass будет утерян навсегда. Дело в том, что по стандарту C++ [1], при раскрутке стека в результате возникновения исключения уничтожаются только полностью созданные объекты, а MyClass таким не является – ведь управление не доходит до тела конструктора. В результате будут вызваны только деструкторы членов класса, а в случае MyClass деструкторы эти ничего не делают, т.к. a_ и b_ являются обыкновенными указателями. Всё это приводит к утечке памяти.
Очевидное решение: вместо обычного указателя использовать объект, хранящий указатель и освобождающий объект в своём деструкторе. Такая технология называется RAII, Resource Acquisition Is Initialization – «Захват ресурса есть инициализация». RAII – это популярный паттерн проектирования, применяемый во многих объектно-ориентированных языках (например, C++, D, Ada), смысл которого заключается в том, что захват ресурса совмещается с инициализацией объекта, а освобождение – с финализацией (уничтожением) объекта. Более подробно о RAII можно почитать в [2], [3] и [4]). Именно таким поведением обладает шаблон auto_ptr из стандартной библиотеки C++.
std::auto_ptr<>
auto_ptr является умным указателем, реализующим семантику владения. Стоит отметить, что auto_ptr – это единственный умный указатель, включенный в нынешний стандарт C++ [1].
Вернёмся к нашим примерам и перепишем их с использованием auto_ptr:
Теперь в обоих случаях код является безопасным с точки зрения исключений, а также простым и элегантным.
Посмотрим на auto_ptr внимательнее. Этот шаблон определён в стандартном заголовке memory:
Сначала мы создаём экземпляр класса A и передаём его во владение умному указателю a. Затем мы создаём ещё один умный указатель b и передаём владение экземпляром A ему. При этом указатель a должен быть сброшен, иначе одним и тем же экземпляром объекта будут владеть два умных указателя, и в результате мы получим двойное удаление одного и того же экземпляра при выходе из области видимости. Аналогичное поведение мы увидим и при присваивании:
Разрушающее копирование – это особенность поведения auto_ptr, заложенная в его архитектуре.
Кроме того, существует пустой шаблон auto_ptr_ref. Он является уловкой реализации, благодаря которой нельзя использовать в операции присваивания константные auto_ptr. Подробнее об этом можно почитать в [5].
Подведём итоги: шаблон auto_ptr обеспечивает владение экземпляром объекта и его корректную очистку при выходе из области видимости – иными словами, реализует принцип RAII. Тем не менее, следует принимать во внимание необычную семантику копирования и присваивания – разрушающее копирование. В частности, эта семантика не позволяет использовать auto_ptr в контейнерах стандартной библиотеки.
Выходом из этой ситуации является реализация умного указателя с подсчётом ссылок – shared_ptr.
std::tr1::shared_ptr<>
shared_ptr является более гибкой и универсальной версией умного указателя. shared_ptr не входит в текущую версию стандарта, однако этот класс уже включён в TR1 (Technical Report 1) [6], что делает его верным кандидатом на включение в следующую редакцию стандарта – C++0x. Рассмотрим объявление этого шаблона:
shared_ptr обладает во многом схожей с auto_ptr семантикой, основные отличия заключаются в методике копирования. Класс shared_ptr реализует разделяемое (shared) владение с подсчётом ссылок, что позволяет использовать более привычные конструкторы копирования и операторы присваивания. Благодаря такой семантике shared_ptr можно использовать в стандартных контейнерах STL.
Кроме того, shared_ptr позволяет задавать функтор удаления (deleter), что может быть полезно для классов, имеющих необычную семантику удаления. В качестве небольшого приятного дополнения – оператор неявного преобразования в bool, что позволяет писать привычное:
В итоге мы имеем реализацию умного указателя, экземпляры которого совместно владеют экземпляром объекта, на который они удерживают ссылку. Совместное владение основано на механизме подсчёта ссылок. Такая семантика позволяет использовать shared_ptr вместо обычного указателя, практически полностью сохраняя поведение.
Представим себе случай, когда нам нужно передать указатель, не передавая права владения объектом. Использовать для этой цели shared_ptr нельзя, т.к. его копирование увеличит счётчик ссылок, и в результате происходит разделение прав владения. Использование обычного указателя также нежелательно, т.к., как уже говорилось в самом начале, невозможно проверить, ссылается указатель на существующий объект или нет.
Специально для такого случая в TR1 предусмотрен ещё один вид умных указателей: weak_ptr.
std::tr1::weak_ptr<>
weak_ptr – это «слабый», не владеющий экземпляром объекта умный указатель. Вот как выглядит его объявление:
weak_ptr ссылается на экземпляр, которым владеет shared_ptr, однако не разделяет прав владения этим экземпляром. Это гарантирует, что если экземпляр уже уничтожен, мы сможем это надёжно и безопасно проверить.
Следует отметить, что хотя shared_ptr и weak_ptr пока ещё не входят в официальный стандарт C++, они уже поддерживаются многими современными компиляторами, например GCC 4.1.2 и Visual Studio 2008.
Ниже приводятся краткие описания ещё нескольких экзотических вариантов умных указателей из библиотеки boost.
boost::intrusive_ptr<>
intrusive_ptr представляет собой облегчённую версию shared_ptr, специально предназначенную для классов, имеющих встроенные механизмы подсчёта ссылок. Для таких классов intrusive_ptr позволяет реализовать эффективный механизм совместного владения с подсчётом ссылок, но без дополнительных затрат. Соответственно, отсутствует и аналог weak_ptr. Во всём остальном семантика intrusive_ptr повторяет семантику shared_ptr.
Для реализации счётчика ссылок intrusive_ptr требует наличия функций intrusive_ptr_add_ref и intrusive_ptr_release, принимающих в качестве параметра указатель на экземпляр класса. intrusive_ptr_add_ref вызывается для увеличения счётчика ссылок, intrusive_ptr_release – для его уменьшения. Есть небольшая хитрость, касающаяся пространства имён этих функций. Для компиляторов, поддерживающих ADL (Argument Dependent Lookup – правило, позволяющее искать свободные функции в пространствах имён их аргументов; более подробно об ADL можно почитать в [2]), можно определить эти функции в пространстве имён, соответствующем классу, экземпляром которого управляет intrusive_ptr. Если ваш компилятор не поддерживает ADL, придётся объявлять эти функции в пространстве имён boost.
boost::scoped_ptr<>
scoped_ptr представляет собой облегчённую версию auto_ptr, запрещающую копирование и присваивание. По сути это всего-навсего аналог const std::auto_ptr<>.
boost::scoped_array<>, boost::shared_array<>
В заключение хочется отметить, что в подавляющем большинстве случаев не рекомендуется использовать динамические массивы, предпочитая им стандартные контейнеры STL, например std::vector. Такой подход позволит избежать множества проблем, почти ничего не теряя.
std::unique_ptr<>
Свежий черновик нового стандарта C++0x объявляет auto_ptr устаревшим (obsolete), предлагая использовать вместо него unique_ptr. Его основное отличие от auto_ptr – это возможность использования функтора удаления (deleter), аналогично тому, как это реализовано в shared_ptr, что обеспечивает дополнительную гибкость. Например, unique_ptr позволяет хранить указатели на массивы, чего не позволяет auto_ptr. Таким образом, unique_ptr заменяет сразу auto_ptr и scoped_array. unique_ptr имеет другую семантику копирования и присваивания, использующую анонсированные в C++0x ссылки на временные объекты (R-value references). В качестве приятного дополнения – неявное преобразование к bool, что позволяет использовать unique_ptr в логических выражениях (опять же, по аналогии с shared_ptr).
Мы не будем подробно рассматривать unique_ptr, т.к. Стандарт C++0x ещё не завершён, а его реализации если и существуют, то не распространены широко.
Другие реализации
Список реализаций умных указателей не ограничивается перечисленными выше классами. Помимо них, существуют ещё реализации в библиотеках Loki, Poco, STLSoft и многие, многие другие. Их семантика и поведение зачастую схожее с описанными выше реализациями, но в некоторых случаях может сильно отличаться. Подробные описания этих реализаций приводятся в документации на соответствующие библиотеки.
О чём следует помнить, используя умные указатели
Рассмотрев, что умеют делать умные указатели, стоит остановиться и на том, чего они не умеют. Мы рассмотрим два основных подводных камня, связанных с использованием умных указателей: циклические ссылки и взаимодействие с обычными (raw) указателями C++.
Циклические ссылки возникают в том случае, если 2 класса напрямую или косвенно ссылаются друг на друга, используя умные указатели:
В таком случае, если мы имеем экземпляры классов A и B, ссылающиеся друг на друга, при попытке удалить один из них произойдёт зацикливание. Пример для auto_ptr выглядит надуманным, более реальный пример – с использованием shared_ptr:
В этом случае зацикливания не произойдёт, однако случится утечка памяти – счётчики ссылок обоих объектов никогда не обнулятся, соответственно и деструкторы вызваны не будут. Следует помнить, что умные указатели не решают проблемы циклических ссылок, ответственность в этом случае целиком лежит на программисте.
Вторая распространённая ошибка – это совместное использование обычных (raw) и умных указателей. Проблема в том, что переходя от умного указателя к обычному, вы сразу же нарушаете контракт владения умного указателя, он уже не контролирует экземпляр объекта, а это может привести к печальным последствиям:
В приведённом выше примере экземпляр объекта будет удалён дважды, а это приведёт скорее всего к ошибке доступа к памяти. Следует всегда помнить, что, переходя от умного указателя к обычному, нужно обнулить умный указатель:
Заключение
При правильном и аккуратном использовании умные указатели могут существенно облегчить жизнь C++-программисту, однако следует очень внимательно изучить их поведение, их сильные и слабые стороны. Надеюсь, данная статья поможет вам в этом. За более подробной информацией рекомендуется обратиться к источникам, перечисленным ниже.
Источники мудрости
Благодарности
Большое спасибо Валерию Артюхину, благодаря чьей критике эта статья стала лучше.