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

std::unique_ptr и std::move

Писать класс-владелец вроде IntBox для каждого типа не нужно. Заголовок (header) <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 уничтожается, его деструктор удаляет число — при любом выходе из функции. Классы-владельцы, которые ведут себя как указатели, называют умными указателями (smart pointers).

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

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

У объекта, которым владеет std::unique_ptr, владелец ровно один. Поэтому копировать unique_ptr нельзя: его конструктор копирования удалён, как у IntBox с = delete. Зато владение можно передать другому 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. Такая передача называется перемещением (move): само число остаётся на месте, меняется только владелец. Разыменовать 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 не хранит, поэтому в функцию его передают отдельным параметром (parameter).

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

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>'

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

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