-
Notifications
You must be signed in to change notification settings - Fork 1
9.«Умные указатели» в С : unique_ptr, shared_ptr, weak_ptr. Использование weak_ptr на примере паттерна итератор.
Smart pointer — это объект, работать с которым можно как с обычным указателем, но при этом, в отличии от последнего, он предоставляет некоторый дополнительный функционал (например, автоматическое освобождение закрепленной за указателем области памяти).
Умные указатели призваны для борьбы с утечками памяти, которые сложно избежать в больших проектах. Они особенно удобны в местах, где возникают исключения, так как при последних происходит процесс раскрутки стека и уничтожаются локальные объекты. В случае обычного указателя — уничтожится переменная-указатель, при этом ресурс останется не освобожденным. В случае умного указателя — вызовется деструктор, который и освободит выделенный ресурс.
Ужесточенные правила владения(копирование запрещено).
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;
// права владения переходят к y_ptr, а x_ptr начинает указывать на null pointer
y_ptr = std::move(x_ptr);
- unique_ptr обладает методами reset(), который сбрасывает права владения, и get(), который возвращает сырой (классический) указатель.
std::unique_ptr<Foo> ptr = std::unique_ptr<Foo>(new Foo);
// получаем классический указатель
Foo *foo = ptr.get();
foo->bar();
// сбрасываем права владения
ptr.reset();
- В отличии от рассмотренного выше указателя, shared_ptr реализует подсчет ссылок на ресурс. Ресурс освободится тогда, когда счетчик ссылок на него будет равен 0. Как видно, система реализует одно из основных правил сборщика мусора.
std::shared_ptr<int> x_ptr(new int(42));
std::shared_ptr<int> y_ptr(new int(13));
// после выполнения данной строчки, ресурс
// на который указывал ранее y_ptr (int(13)) освободится,
// а на int(42) будут ссылаться оба указателя
y_ptr = x_ptr;
std::cout << *x_ptr << "\t" << *y_ptr << std::endl;
// int(42) освободится лишь при уничтожении последнего ссылающегося
// на него указателя
- Также данный класс имеет методы get() и reset().
auto ptr = std::make_shared<Foo>();
Foo *foo = ptr.get();
foo->bar();
ptr.reset();
- При работе с умным указателем, следует опасаться их создания "на лету". Например, следующий код может привести к утечки памяти.
someFunction(std::shared_ptr<Foo>(new Foo), getRandomKey());
Стандарт C++ не определяет порядок вычисления аргументов. Может случиться так, что сначала выполнится new Foo
, затем getRandomKey()
и лишь затем конструктор shared_ptr
. Если же функция getRandomKey()
бросит исключение, до конструктора shared_ptr
дело не дойдет, хотя ресурс (объект Foo
) был уже выделен.
В случае с shared_ptr
есть выход — использовать фабричную функцию std::make_shared<>
, которая создает объект заданного типа и возвращает shared_ptr
указывающий на него.
someFunction(std::make_shared<Foo>(), getRandomKey());
make_shared
возвращает shared_ptr
. Этот результат является временным объектом, а стандарт C++ четко декларирует, что временные объекты уничтожаются, в случае появления исключения.
- Данный класс позволяет разрушить циклическую зависимость, которая, может образоваться при использовании
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.