Статьи → Введение в C++11: умные указатели
Продолжу доброю традицию и расскажу сегодня об умных указателях, также известных как Smart Pointers. Умные указатели очень актуальны в мире C++ и новый стандарт не обошел их стороной.
Smart pointer — это объект, работать с которым можно как с обычным указателем, но при этом, в отличии от последнего, он предоставляет некоторый дополнительный функционал (например, автоматическое освобождение закрепленной за указателем области памяти).
Умные указатели призваны для борьбы с утечками памяти, которые сложно избежать в больших проектах. Они особенно удобны в местах, где возникают исключения, так как при последних происходит процесс раскрутки стека и уничтожаются локальные объекты. В случае обычного указателя — уничтожится переменная-указатель, при этом ресурс останется не освобожденным. В случае умного указателя — вызовется деструктор, который и освободит выделенный ресурс.
В новом стандарте появились следующие умные указатели: unique_ptr, shared_ptr и weak_ptr. Все они объявлены в заголовочном файле <memory>.
unique_ptr
Этот указатель пришел на смену старому и проблематичному auto_ptr. Основная проблема последнего заключается в правах владения. Объект этого класса теряет права владения ресурсом при копировании (присваивании, использовании в конструкторе копий, передаче в функцию по значению).
std::auto_ptr<int> x_ptr(new int(42)); std::auto_ptr<int> y_ptr; // вот это нехороший и неявный момент // права владения ресурсов уходят в y_ptr и x_ptr начинает // указывать на null pointer y_ptr = x_ptr; // segmentation fault std::cout << *x_ptr << std::endl;
Это очень неудобно, при работе с контейнером из умных указателей. Банальное
std::vector<std::auto_ptr<int> > vec; // ... std::auto_ptr<int> tmp = vec[0];
сделает элемент вектора невалидным. Именно поэтому данный класс не пользовался популярностью среди разработчиков.
В отличии от auto_ptr, unique_ptr запрещает копирование.
std::unique_ptr<int> x_ptr(new int(42)); std::unique_ptr<int> y_ptr; // ошибка при компиляции y_ptr = x_ptr; // ошибка при компиляции std::unique_ptr<int> z_ptr(x_ptr);
Изменение прав владения ресурсом осуществляется с помощью вспомогательной функции std::move (которая является частью механизма перемещения).
std::unique_ptr<int> x_ptr(new int(42)); std::unique_ptr<int> y_ptr; // также, как и в случае с ``auto_ptr``, права владения переходят // к y_ptr, а x_ptr начинает указывать на null pointer y_ptr = std::move(x_ptr);
Как auto_ptr, так и unique_ptr обладают методами reset(), который сбрасывает права владения, и get(), который возвращает сырой (классический) указатель.
std::unique_ptr<Foo> ptr = std::unique_ptr<Foo>(new Foo); // получаем классический указатель Foo *foo = ptr.get(); foo->bar(); // сбрасываем права владения ptr.reset();
Как видно, unique_ptr недалеко ушел от своего предшественника в плане удобства использования, но, во всяком случае, он обезопасил от неявных смен прав владений ресурсом.
weak_ptr
Этот указатель также, как и shared_ptr начал свое рождение в проекте boost, затем был включен в C++ Technical Report 1 и, наконец, пришел в новый стандарт.
Данный класс позволяет разрушить циклическую зависимость, которая, несомненно, может образоваться при использовании shared_ptr. Предположим, есть следующая ситуация (переменные-члены не инкапсулированы для упрощения кода)
class Bar; class Foo { public: Foo() { std::cout << "Foo()" << std::endl; } ~Foo() { std::cout << "~Foo()" << std::endl; } std::shared_ptr<Bar> bar; }; class Bar { public: Bar() { std::cout << "Bar()" << std::endl; } ~Bar() { std::cout << "~Bar()" << std::endl; } std::shared_ptr<Foo> foo; }; int main() { auto foo = std::make_shared<Foo>(); foo->bar = std::make_shared<Bar>(); foo->bar->foo = foo; return 0; }
Как видно, объект foo ссылается на bar и наоборот. Образован цикл, из-за которого не вызовутся деструкторы объектов. Для того чтобы разорвать этот цикл, достаточно в классе Bar заменить shared_ptr на weak_ptr.
Почему образован цикл? Давайте разберемся. При выходе из блока (в данном случае функции main()) уничтожаются локальные объекты. Локальным объектом является foo. При уничтожении foo счетчик ссылок на его ресурс уменьшится на единицу. Однако, ресурс освобожден не будет, так как на него есть ссылка со стороны ресурса bar. А на bar есть ссылка со стороны того же ресурса foo.
weak_ptr не позволяет работать с ресурсом напрямую, но зато обладает методом lock(), который генерирует shared_ptr().
std::shared_ptr<Foo> ptr = std::make_shared<Foo>(); std::weak_ptr<Foo> w(ptr); if (std::shared_ptr<Foo> foo = w.lock()) { foo->doSomething(); }
Вместо заключения
Умные указатели — очень удобная и полезная вещь, но я рассмотрел их поверхностно, лишь их концептуальные части. За полным списком их возможностей следует обращаться к документации.
Стоит отметить, что рассмотренные мною умные указатели (кроме unique_ptr) не предназначен для владения массивами. Это связано с тем, что деструктор вызывает именно delete, а не delete[] (что требуется для массивов).
Для unique_ptr мы имеем дело с предопределенной специализацией для массивов. Для ее использования необходимо указать [] возле параметра шаблона. Выглядит это так.
std::unique_ptr<Foo[]> arr(new Foo[2]); arr[0].doSomething();
Как заметил в комментариях [@GooRoo](#comment-1), в boost есть специальный класс shared_array<>, но он в новый стандарт включен не был.
Конструктивная критика:
Правда, через [] к массиву небось всё равно не обратишься в таком случае, да? В boost не парились и сделали shared_array<>.
Подъёб:
А чё ты пробел ставишь между закрывающими угловыми скобками в шаблонах? На С++11 же пишешь ;)
Написал.
Это не панацея, ибо:
А вот про shared_array упомянул, спасибо.
Old-School привычка ;) Надо переучиваться.
Блин, не гони беса. Напиши в самом начале. Человек читает статью, видит примеры кода — сразу хочется проверить. А какой хедер подключать — х. его з.
P.S. И сделай cookies тут. Я запарился заново вбивать свой ник, мыло и адрес блога.
Я просто хочу вообще отказаться от этой формы и прикрутить авторизацию через Twitter/Google и OpenID. Да вот никак руки не дойдут. Вечером прикручу, если не забуду.
Чувак, надеюсь, ты ж без велосипедов, да? ) Готовых куча. Взять тот же Disqus.com
Хотя не, нахер Disqus. Там нет подсветки синтаксиса.