Класс shared_ptr: совместно используемый указатель

Шаблонный класс shared_ptr реализует совместно используемый указатель. Такой указатель можно и копировать, и перемещать. Количество копий отслеживается с помощью счетчика. Когда счетчик становится равным нулю, выполняется освобождение динамической памяти. Объявление класса shared_ptr:

template<typename _Tp>
    class shared_ptr : public __shared_ptr<_Tp>;

Управление одним объектом

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

// #include <memory>
std::shared_ptr<B> ptr1;
std::shared_ptr<B> ptr2(nullptr);
std::shared_ptr<B> ptr3(new B(20));
std::shared_ptr<B> ptr4(ptr3);
std::unique_ptr<B> uptr(new B(5));
std::shared_ptr<B> ptr5(std::move(uptr));
std::cout << ptr1.use_count() << std::endl;  // 0
std::cout << ptr2.use_count() << std::endl;  // 0
std::cout << ptr3.use_count() << std::endl;  // 2
std::cout << ptr4.use_count() << std::endl;  // 2
std::cout << ptr5.use_count() << std::endl;  // 1
std::cout << (uptr == nullptr) << std::endl; // 1

После названия класса shared_ptr внутри угловых скобок задается название класса объекта или другой тип данных, например, int. Первые две инструкции создают объекты с нулевым указателем, а третья инструкция при создании сразу принимает право владения экземпляром класса B, созданном в динамической памяти. Четвертая инструкция создает копию «умного» указателя. В результате счетчик ссылок у обоих указателей будет равен 2. Получить значение счетчика позволяет метод use_count(). В пятой инструкции создается указатель класса unique_ptr, а в шестой инструкции выполняется передача владения объектом указателю класса shared_ptr. При этом объект uptr будет хранить нулевой указатель.

Когда счетчик становится равным нулю, выполняется освобождение динамической памяти, причем только один раз:

{
   std::shared_ptr<B> ptr1(new B(20));            // B::B()
   std::cout << ptr1.use_count() << std::endl;    // 1
   {
      std::shared_ptr<B> ptr2(ptr1);
      std::cout << ptr1.use_count() << std::endl; // 2
      std::cout << ptr2.use_count() << std::endl; // 2
   }
   std::cout << ptr1.use_count() << std::endl;    // 1
}                                                 // B::~B()
// Динамическая память освобождается

При динамическом создании объекта может возникнуть исключение еще до получения права владения. Чтобы этого избежать следует воспользоваться функцией make_shared(). Прототип функции:

template<typename _Tp, typename... Args>
   inline shared_ptr<_Tp>
   make_shared(Args &&... __args)

Пример:

std::shared_ptr<A> ptrA = std::make_shared<A>();
std::cout << ptrA->x << std::endl; // 0
auto ptrB = std::make_shared<B>(20);
std::cout << ptrB->y << std::endl; // 20

Класс shared_ptr перегружает операторы -> (доступ к члену) и * (разыменование), а также операторы сравнения:

auto ptrB = std::make_shared<B>(20);         // B::B()
std::cout << ptrB->y << std::endl;           // 20
std::cout << (*ptrB).y << std::endl;         // 20
auto ptr = std::make_shared<int>(30);
std::cout << *ptr << std::endl;              // 30
ptrB.reset(); // Очистка (B::~B())
if (!ptrB) std::cout << "null" << std::endl; // null

Кроме того, выполнена перегрузка оператора <<, позволяющего вывести результат выполнения метода get() на консоль:

auto ptr = std::make_shared<int>(10);
std::cout << ptr << std::endl;   // Адрес (например, 0x586e60)
std::cout << *ptr << std::endl;  // Значение (10)

Класс shared_ptr реализует совместно используемый указатель. Такой указатель можно и копировать, и перемещать:

std::shared_ptr<B> ptr1;
std::shared_ptr<B> ptr2;
std::shared_ptr<B> ptr3 = std::make_shared<B>(20);
// Можно создать копию
ptr1 = ptr3;
std::cout << ptr1.use_count() << std::endl; // 2
// Можно перемещать
ptr2 = std::move(ptr1);
std::cout << ptr2.use_count() << std::endl; // 2
std::cout << ptr1.use_count() << std::endl; // 0
// Можно перемещать из unique_ptr
std::unique_ptr<B> uptr(new B(5));
ptr1 = std::move(uptr);
std::cout << ptr1.use_count() << std::endl; // 1

Перечислим основные методы класса shared_ptr:

  • use_count() — возвращает количество объектов, совместно использующих указатель (возвращает значение счетчика):
auto ptr = std::make_shared<B>(20);
std::cout << ptr.use_count() << std::endl; // 1
  • unique() — возвращает значение true, если значение счетчика равно 1, и false — в противном случае:
auto ptr = std::make_shared<B>(20);
if (ptr.unique())
   std::cout << "unique" << std::endl;     // unique
else std::cout << "no" << std::endl;
  • get() — возвращает обычный указатель (или нулевой указатель), при этом сохраняя право на владение объектом:
auto ptr = std::make_shared<B>(20);
B *pB = ptr.get();
std::cout << pB->y << std::endl;           // 20
std::cout << ptr.use_count() << std::endl; // 1
  • swap() — меняет местами значения двух объектов:
auto ptr1 = std::make_shared<B>(10);
auto ptr2 = std::make_shared<B>(20);
ptr1.swap(ptr2);
std::cout << ptr1->y << std::endl; // 20
std::cout << ptr2->y << std::endl; // 10

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

auto ptr1 = std::make_shared<B>(10);
auto ptr2 = std::make_shared<B>(20);
std::swap(ptr1, ptr2);
std::cout << ptr1->y << std::endl; // 20
std::cout << ptr2->y << std::endl; // 10
  • reset() — уменьшает значение счетчика на 1 и обнуляет указатель в текущем объекте. Если значение счетчика будет равно 0, то освобождает динамическую память. Если в качестве параметра передан указатель, то «умный» указатель получает право собственности на него:
auto ptr1 = std::make_shared<B>(20);        // B::B()
ptr1.reset();                               // B::~B()
std::cout << ptr1.use_count() << std::endl; // 0
auto ptr2 = std::make_shared<B>(30);        // B::B()
B *pB = new B(40);                          // B::B()
ptr2.reset(pB);                             // B::~B()
std::cout << ptr2.use_count() << std::endl; // 1
std::cout << ptr2->y << std::endl;          // 40
// B::~B()

Для приведения типов можно воспользоваться следующими функциями:

  • static_pointer_cast() — стандартное приведение типов (static_cast). Прототип функции:
template<typename _Tp, typename _Up>
   inline shared_ptr<_Tp>
   static_pointer_cast(const shared_ptr<_Up> &sp) noexcept;
  • dynamic_pointer_cast() — выполняет приведение типов указателей или ссылок (dynamic_cast). Применяется для приведения полиморфных типов. Прототип функции:
template<typename _Tp, typename _Up>
   inline shared_ptr<_Tp>
   dynamic_pointer_cast(const shared_ptr<_Up> &sp) noexcept;
const_pointer_cast() — приведение const_cast. Прототип функции:
template<typename _Tp, typename _Up>
   inline shared_ptr<_Tp>
   const_pointer_cast(const shared_ptr<_Up> &sp) noexcept;
  • reinterpret_pointer_cast() — приведение reinterpret_cast. Функция доступна, начиная со стандарта C++17. Прототип функции:
template<typename _Tp, typename _Up>
   inline shared_ptr<_Tp>
   reinterpret_pointer_cast(const shared_ptr<_Up> &sp) noexcept;

Управление массивом

Начиная со стандарта C++17, класс shared_ptr можно также использовать для работы с динамическими массивами. Чтобы вызывался оператор delete для массива нужно внутри угловых скобок после типа указать квадратные скобки:

std::shared_ptr<A[]> ptr(new A[2]);
ptr[0].x = 10;
ptr[1].x = 20;
std::cout << ptr[0].x << std::endl; // 10
std::cout << ptr[1].x << std::endl; // 20

Как видно из примера, для доступа к элементу массива используются квадратные скобки, внутри которых указывается индекс элемента внутри массива.

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

Помощь сайту

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

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