Умные указатели C++

Материал из SEWiki
Перейти к: навигация, поиск

Введение

Умные указатели (УА) (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[] для хранимого указателя

Умные указатели с раздельным владением

Умные указатели с раздельным владением предназначены для того, что бы разделять один объект между несколькими клиентами. Основное отличие указателей этого типа заключается в стратегии подсчета ссылок.

boost::shared_ptr

Самый мощный и умный указатель. Использует счетчик ссылок в динамической памяти.

Особенности

  • можно и нужно использовать в контейнерах
  • сохраняет ссылку на функцию 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_array

Аналог 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(); }