Перейти к содержимому
Теория · Память и умные указатели · урок 6 из 12

std::unique_ptr и std::move

Писать класс-владелец вроде IntBox для каждого типа не нужно. Заголовок <memory> даёт готового владельца — std::unique_ptr:

C++
#include <iostream>
#include <memory>
#include <utility>

void print_square(int x) {
    std::unique_ptr<int> square = std::make_unique<int>(x * x);
    if (*square > 100) {
        return;
    }
    std::cout << *square << '\n';
}
int main() {
    print_square(12);
    print_square(5);  // 25
}

std::make_unique<int>(x * x) создаёт в куче число int и возвращает std::unique_ptr<int>, который им владеет. Через *square число читают, как через обычный указатель. Когда square уничтожается, его деструктор удаляет число — при любом выходе из функции. Классы-владельцы, которые ведут себя как указатели, называют умными указателями.

Во владение можно отдать и адрес из new: std::unique_ptr<int> p(new int{5});. Но с std::make_unique в коде не остаётся ни одного new, а значит, и ни одного места, где нужно искать парный delete.

Один владелец

У объекта, которым владеет std::unique_ptr, владелец ровно один. Поэтому копировать unique_ptr нельзя: его конструктор копирования удалён, как у IntBox с = delete.

Вспомните камеру хранения из первого урока темы: unique_ptr — единственный номерок от ячейки. Сделать его дубликат нельзя, зато номерок можно отдать другому человеку. Так и владение можно передать другому unique_ptr через std::move из заголовка <utility>:

C++
std::unique_ptr<int> a = std::make_unique<int>(7);
std::unique_ptr<int> b = std::move(a);
std::cout << *b << ' ' << (a == nullptr) << '\n';  // 7 1

После этого числом владеет b, а a пуст и равен nullptr. Такая передача называется перемещением: само число остаётся на месте, меняется только владелец. Разыменовать a после перемещения — то же, что разыменовать nullptr, то есть неопределённое поведение.

unique_ptr возвращают из функции по значению, и std::move для этого не нужен. Локальная переменная в return перемещается сама: после выхода из функции она всё равно будет уничтожена.

Массив во владении

std::unique_ptr<int[]> владеет массивом и удаляет его через delete[]. Такой массив создаёт std::make_unique<int[]>(n), и все его n чисел равны нулю. К элементам обращаются по индексу, как через указатель:

C++
int n = 3;
std::unique_ptr<int[]> squares = std::make_unique<int[]>(n);
for (int i = 0; i < n; ++i) {
    squares[i] = i * i;
}
std::cout << squares[n - 1] << '\n';  // 4

unique_ptr не хранит размер массива, поэтому размер передают в функцию отдельным параметром.

Частая ошибка

unique_ptr передают в функцию по значению:

C++
#include <iostream>
#include <memory>

void show(std::unique_ptr<int> p) {
    std::cout << *p << '\n';
}

int main() {
    std::unique_ptr<int> a = std::make_unique<int>(7);
    show(a);  // ✗
}
TEXT
error: call to deleted constructor of 'std::unique_ptr<int>'

Параметр по значению — копия аргумента, а копировать unique_ptr нельзя. В задачах курса компилятор описывает эту ошибку иначе: call to implicitly-deleted copy constructor of 'std::unique_ptr<int>'.

Если функция только читает число, объявите параметр как const std::unique_ptr<int>& p. Если она должна забрать владение, вызовите show(std::move(a)), и после вызова a будет пуст.

Главное

  • std::unique_ptr — единственный владелец объекта: он удаляет объект в своём деструкторе.
  • Создают его через std::make_unique, и тогда в коде нет ни new, ни delete.
  • Копировать unique_ptr нельзя, а передать владение можно через std::move; после этого источник пуст.
  • std::unique_ptr<int[]> владеет массивом и удаляет его через delete[].

Подпишись, чтобы пройти курс

Курс бесплатный. Войди через Telegram и подпишись на канал — откроются все уроки.

Войти другим способом