Класс weak_ptr: «слабый» указатель

При использовании класса shared_ptr можно получить циклическую зависимость, если первый объект ссылается на второй объект, а второй на первый, или объект ссылается сам на себя. Счетчик в данном случае никогда не станет равным нулю, а, следовательно, не будет освобождена динамическая память. Давайте рассмотрим пример. Предположим, у нас есть класс C:

class C {
public:
   std::shared_ptr<C> ptr;
   C() { std::cout << "C::C()" << std::endl; }
   ~C() { std::cout << "C::~C()" << std::endl; }
};

Если мы выполним следующие инструкции, то получим циклическую зависимость:

auto p = std::make_shared<C>();
p->ptr = p;
// Циклическая зависимость!!! Утечка памяти!!!

Решением проблемы являются «слабые» указатели, реализуемые с помощью класса weak_ptr. Достаточно в классе C объявить поле ptr с типом weak_ptr:

class C {
public:
   std::weak_ptr<C> ptr;
   C() { std::cout << "C::C()" << std::endl; }
   ~C() { std::cout << "C::~C()" << std::endl; }
};

Класс weak_ptr владельцем указателя не считается, поэтому при использовании следующих инструкций никакой циклической зависимости не будет:

auto p = std::make_shared<C>();
p->ptr = p; // OK

Объявление класса weak_ptr:

template<typename _Tp>
   class weak_ptr : public __weak_ptr<_Tp>;

Создать экземпляр класса weak_ptr можно следующими основными способами (полный список смотрите в документации):

// #include <memory>
std::weak_ptr<B> ptr1;
std::weak_ptr<B> ptr2(ptr1);
std::shared_ptr<B> sptr(new B(20));
std::weak_ptr<B> ptr3(sptr);
std::weak_ptr<B> ptr4(std::move(ptr3));
std::cout << ptr1.use_count() << std::endl;  // 0
std::cout << ptr2.use_count() << std::endl;  // 0
std::cout << ptr3.use_count() << std::endl;  // 0
std::cout << ptr4.use_count() << std::endl;  // 1
std::cout << sptr.use_count() << std::endl;  // 1

После названия класса weak_ptr внутри угловых скобок задается название класса объекта или другой тип данных, например, int. Первая инструкция создает объект с нулевым указателем, а вторая инструкция создает копию объекта. В третьей инструкции создается объект класса shared_ptr, а в четвертой инструкции на его основе создается объект класса weak_ptr. В пятой инструкции передаются права на владение объектом, при этом объект ptr3 будет хранить нулевой указатель.

Объект класса weak_ptr является оболочкой над объектом класса shared_ptr. Правом владения указателя и ответственным за освобождение динамической памяти является объект класса shared_ptr. Использовать объект напрямую нельзя, т. к. класс weak_ptr не перегружает операторы -> и *, а также не содержит метода get(). Чтобы получить доступ, нужно выполнить преобразование в объект класса shared_ptr. Сделать это можно двумя способами:

  • передать объект класса weak_ptr конструктору класса shared_ptr. Если объект класса weak_ptr пустой, то генерируется исключение. Пример:
auto sptr = std::make_shared<B>(20);
std::weak_ptr<B> ptr(sptr);
std::shared_ptr<B> sp(ptr);
std::cout << sp->y << std::endl;  // 20
  • с помощью метода lock(). Если объект класса weak_ptr пустой, то возвращается пустой объект класса shared_ptr. Пример:
auto sptr = std::make_shared<B>(20);
std::weak_ptr<B> ptr(sptr);
std::shared_ptr<B> sp;
sp = ptr.lock();
if (sp) std::cout << sp->y << std::endl;  // 20

С помощью оператора = можно создать копию объекта класса weak_ptr, а также присвоить объект класса shared_ptr. Начиная со стандарта C++14, существует возможность перемещения:

auto sptr1 = std::make_shared<B>(20);
auto sptr2 = std::make_shared<B>(10);
std::weak_ptr<B> wptr1(sptr1);
std::weak_ptr<B> wptr2;
// Можно создать копию
wptr2 = wptr1;
std::shared_ptr<B> sp;
sp = wptr2.lock();
if (sp) std::cout << sp->y << std::endl;     // 20
// Можно присвоить объект класса shared_ptr
wptr2 = sptr2;
sp = wptr2.lock();
if (sp) std::cout << sp->y << std::endl;     // 10
// Можно перемещать
wptr1 = std::move(wptr2);
std::cout << wptr1.use_count() << std::endl; // 2
std::cout << wptr2.use_count() << std::endl; // 0

Класс weak_ptr содержит следующие основные методы:

  • use_count() — возвращает количество объектов, совместно использующих указатель (возвращает значение счетчика):
auto ptr = std::make_shared<B>(20);
std::weak_ptr<B> wptr(ptr);
std::cout << wptr.use_count() << std::endl; // 1
  • expired() — возвращает значение true, если значение счетчика равно 0, и false — в противном случае:
std::weak_ptr<B> wptr;
std::cout << std::boolalpha;
std::cout << wptr.expired() << std::endl;   // true
auto ptr = std::make_shared<B>(20);
wptr = ptr;
std::cout << wptr.expired() << std::endl;   // false
  • lock() — возвращает объект класса shared_ptr. Если объект класса weak_ptr пустой, то возвращается пустой объект класса shared_ptr:
auto sptr = std::make_shared<B>(20);
std::weak_ptr<B> ptr(sptr);
std::shared_ptr<B> sp;
sp = ptr.lock();
if (sp) std::cout << sp->y << std::endl;    // 20
  • swap() — меняет местами значения двух объектов:
auto sptr1 = std::make_shared<B>(10);
auto sptr2 = std::make_shared<B>(20);
std::weak_ptr<B> wptr1(sptr1);
std::weak_ptr<B> wptr2(sptr2);
wptr1.swap(wptr2);
std::cout << wptr1.lock()->y << std::endl;  // 20
std::cout << wptr2.lock()->y << std::endl;  // 10

Вместо метода swap() можно воспользоваться функцией swap():

auto sptr1 = std::make_shared<B>(10);
auto sptr2 = std::make_shared<B>(20);
std::weak_ptr<B> wptr1(sptr1);
std::weak_ptr<B> wptr2(sptr2);
std::swap(wptr1, wptr2);
std::cout << wptr1.lock()->y << std::endl;  // 20
std::cout << wptr2.lock()->y << std::endl;  // 10
  • reset() — делает объект пустым:
auto sptr = std::make_shared<B>(10);
std::weak_ptr<B> wptr(sptr);
std::cout << wptr.use_count() << std::endl; // 1
wptr.reset();
std::cout << wptr.use_count() << std::endl; // 0

Учебник C++ (MinGW-W64)
Учебник C++ (MinGW-W64) в формате PDF

Помощь сайту

ЮMoney (Yandex-деньги): 410011140483022

ПАО Сбербанк:
Счет: 40817810855006152256
Реквизиты банка:
Наименование: СЕВЕРО-ЗАПАДНЫЙ БАНК ПАО СБЕРБАНК
Корреспондентский счет: 30101810500000000653
БИК: 044030653
КПП: 784243001
ОКПО: 09171401
ОКОНХ: 96130
Скриншот реквизитов