Класс unique_ptr: уникальный указатель

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

template <typename _Tp, typename _Dp = default_delete<_Tp>>
    class unique_ptr;
template<typename _Tp, typename _Dp>
    class unique_ptr<_Tp[], _Dp>;

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

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

std::unique_ptr<B> ptr1;
std::unique_ptr<B> ptr2(nullptr);
std::unique_ptr<B> ptr3(new B(20));

После названия класса unique_ptr внутри угловых скобок задается название класса объекта или другой тип данных, например, int. Первые две инструкции создают объекты с нулевым указателем, а последняя инструкция при создании сразу принимает право владения экземпляром класса B, созданном в динамической памяти. Объекты «умных» указателей создаются в локальной области видимости, поэтому при выходе из области видимости будет вызван деструктор «умного» указателя, внутри которого будет освобождена динамическая память и вызван деструктор объекта. Теперь даже при возникновении исключения динамическая память будет освобождена:

try {
   // #include <memory>
   std::unique_ptr<B> ptr(new B(20));
   // Выполняем операции
   throw std::exception();
   std::cout << ptr->y << std::endl;
} catch (std::exception &ex) {
   std::cout << "Исключение обработано" << std::endl;
}

Результат (деструктор был вызван):

B::B()
B::~B()
Исключение обработано

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

B::B()
20
B::~B()

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

template<typename _Tp, typename... _Args>
   inline typename _MakeUniq<_Tp>::__single_object
   make_unique(_Args &&... args);
template<typename _Tp>
   inline typename _MakeUniq<_Tp>::__array
   make_unique(size_t num);

Пример:

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

При объявлении объекта удобно использовать ключевое слово auto:

auto ptrA = std::make_unique<A>();
std::cout << ptrA->x << std::endl; // 0

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

auto ptrB = std::make_unique<B>(20);
std::cout << ptrB->y << std::endl;        // 20
std::cout << (*ptrB).y << std::endl;      // 20
auto ptr = std::make_unique<int>(30);
std::cout << *ptr << std::endl;           // 30
std::unique_ptr<B> p;
std::cout << (p == nullptr) << std::endl; // 1
if (!p) std::cout << "null" << std::endl; // null

Класс unique_ptr реализует уникальный указатель на объект. Такой указатель нельзя копировать, но можно передавать владение им другому «умному» указателю с помощью оператора присваивания и функции move():

std::unique_ptr<B> ptr1;
std::unique_ptr<B> ptr2 = std::make_unique<B>(20);
// Нельзя создавать копию
// ptr1 = ptr2;            // Ошибка
// Можно перемещать
ptr1 = std::move(ptr2);    // OK
std::cout << ptr1->y << std::endl;           // 20
std::cout << (ptr2 == nullptr) << std::endl; // 1

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

  • get() — возвращает обычный указатель или нулевой указатель, при этом сохраняя право на владение объектом:
auto ptr = std::make_unique<B>(20);
B *pB = ptr.get();
std::cout << pB->y << std::endl; // 20
  • release() — возвращает обычный указатель или нулевой указатель, при этом передавая право на владение объектом. За освобождение динамической памяти «умный» указатель больше не отвечает, поэтому нужно выполнить освобождение самостоятельно:
auto ptr = std::make_unique<B>(20);
B *pB = ptr.release();
std::cout << pB->y << std::endl;            // 20
std::cout << (ptr == nullptr) << std::endl; // 1
// Нужно освободить память
delete pB;
  • swap() — меняет местами значения двух объектов:
auto ptr1 = std::make_unique<B>(10);
auto ptr2 = std::make_unique<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_unique<B>(10);
auto ptr2 = std::make_unique<B>(20);
std::swap(ptr1, ptr2);
std::cout << ptr1->y << std::endl; // 20
std::cout << ptr2->y << std::endl; // 10
  • reset() — освобождает динамическую память и обнуляет указатель:
auto ptr = std::make_unique<B>(20);
ptr.reset();
std::cout << (ptr == nullptr) << std::endl; // 1

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

Класс unique_ptr можно также использовать для работы с динамическими массивами, но следует учитывать, что по умолчанию вызывается оператор delete для одного объекта, а не для массива. Чтобы вызывался оператор delete для массива нужно внутри угловых скобок после типа указать квадратные скобки:

std::unique_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

Пример создания динамического массива с помощью функции make_unique():

auto ptr = std::make_unique<A[]>(2); // 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
Скриншот реквизитов