Умные указатели C++
Содержание
Введение
Умные указатели (УА) (smart pointers) используюу принцип "Получение ресурса есть инициализация" (RAII) для автоматизации управления памятью. Использование правильного умного указателя упростит и обезопасит ваш код, а так же поможет избежать memory leak-ов.
Виды умных указателей
Умные указатели можно разделить на три вида:
- с безраздельным владением:
- std::auto_ptr
- boost::scoped_ptr
- boost::scoped_array
- с раздельным владением:
- boost::shared_ptr
- boost::shared_array
- boost::intrusive_ptr
Умные указатели с безраздельным владением
Умные указатели этого вида предназначены для хранения на стэке или для включения в класс.
Особняком в этом классе держится std::auto_ptr. С него и начнем.
std::auto_ptr
std::auto_ptr спроектирован для того, чтобы сделать код безопасным с точки зрения исключений. Использование std::auto_ptr позволяет писать код, который отвечает строгой гарантии относительно исключений (strong exception guarantee). Подробное описание того, как с помощью std::auto_ptr сделать Ваш код безопасным нужно читать в книге Герба Саттера "Решение сложных задач на C++" (вторая глава).
Основное отличие std::auto_ptr от умных указателей с безраздельным владением заключается в наличии у него конструктора копирования, оператора присваивания и метода release. При копировании (как и при присваивании) std::auto_ptr передает владение объектом, а метод release() позволяет явно отобрать владение объектом у std::auto_ptr.
std::auto_ptr<A> p1, p2;
p1.reset( new A() ); // p1 владеет A, p2 == NULL
p2 = p1; // p1 передает владение p2. Теперь p1 == NULL, а p2 владеет A
std::auto_ptr<A> p3(p2); // p2 передает владение p3
A * p4 = p3.release(); // p3 передает владение p4
Такое поведение позволяет безопасно передавать в функцию и возвращать из функции объекты, которые могут бросать исключения при копировании, а также избегать ликов.
std::auto_ptr<A> foo(int a)
{
return std::auto_ptr<A>(new A(a));
}
void bar(std::auto_ptr<A> pa)
{
throw 1;
}
int main()
{
std::auto_ptr<A> pa;
pa = foo(10); // Здесь все ок - ликов нет
foo(20); // Здесь тоже все ок, не смотря на то, что возвращаемое значение проигнорировано
bar(pa); // И здесь все тоже будет хорошо
}
Все это делает std::auto_ptr особенным объектом, которые не имеет семантики значения. Именно по этому его нельзя использовать в контейнерах (да и не получится =) ).
Особенности
- его нельзя использовать в контейнерах
- если std::auto_ptr - член класса, то обязятельно переопределите конструктор копирования и оператор присваивания или явно запретите их (к примеру, при помощи boost::noncopyable)
- std::auto_ptr не имеет аналогов в boost-е
- в VS 8.0 реализация std::auto_ptr содержит ошибку.
std::auto_ptr<int > p;
p = new int(5); // в этом месте программа упадет.
// В VS7 такой код не скомпилируется, т.к. отсутствует operator=(T *)
идиома "const std::auto_ptr" - простой способ получить аналог boost::scoped_ptr. const гарантирует, что не произойдет передачи владения.
std::auto_ptr<T> create_new_T();
int main()
{
std::auto_ptr<T> const p = create_new_T();
std::auto_ptr<T> q = p; // это не скомпилируется, благодаря const
return 0;
}
boost::scoped_ptr
Классический умный указатель с безраздельным владением.
Особенности
- его нельзя использовать в контейнерах
- нет метода release
- нет конструктора копирования и оператора присваивания
boost::scoped_array
Аналог boost::scoped_ptr для массивов. Отличается от boost::scoped_ptr тем, что удаляет хранимый указатель при помощи delete[].
Особенности
- его нельзя использовать в контейнерах
- нет метода release
- вызывает delete[] для хранимого указателя
Умные указатели с раздельным владением
Умные указатели с раздельным владением предназначены для того, что бы разделять один объект между несколькими клиентами. Основное отличие указателей этого типа заключается в стратегии подсчета ссылок.
Самый мощный и умный указатель. Использует счетчик ссылок в динамической памяти.
Особенности
- можно и нужно использовать в контейнерах
- сохраняет ссылку на функцию delete того модуля, в котором объект был выделен.
(boost::shared_ptr можно спокойно передать между модулями с разным рантаймом)
- счетчик ссылок защищен для работы в разных потоках
(это можно отключить при помощи #define BOOST_SP_DISABLE_THREADS)
- позволяет хранить указатели разных типов на один объект
struct A {};
struct B : A {};
boost::shared_ptr<B> pb (new B());
boost::shared_ptr<A> pa (static_pointer_cast(pb));
boost::shared_ptr<B> pb2 (dynamic_pointer_cast(pa));
boost::shared_ptr<const B> pb3 (const_pointer_cast(pa));
Аналог boost::shared_ptr для хранения массивов объектов.
boost::intrusive_ptr
Данный умный указатель требует существования двух функций intrusive_ptr_add_ref и intrusive_ptr_add_release, при помощи которых происходит подсчет ссылок. Обычно это означает, что счетчик хранится в самом объекте.
Особенности
- можно и нужно использовать в контейнерах
- требует определения функций intrusive_ptr_add_ref и intrusive_ptr_add_release.
- можно спокойно передать в функцию "чистый" указатель, полученный через get(), а внутри функции снова обернуть его в boost::intrusive_ptr (как часто делается с _com_ptr_t)
- не существует аналогичного указателя для хранения массивов
- имеет operator=(T*)
Умные указатели и boost::bind
Для того, что бы умные указатели работали с boost::bind требуется определить для них функцию
T * get_pointer( pointer<T> const& p );
Для большинства умных указателей, которые можно хранить в STL-ных контейнерах, эта функция уже определена. Для того, что бы заставить работать boost::bind с вашим умным указателем добавить следующий код
#define BOOST_MEM_FN_ENABLE_STDCALL
#include <boost/bind.hpp>
//....
template< class T >
T * get_pointer( MySmartPtr< T > const& p ) { return p.get(); }