std::unique_ptr и std::move
Писать класс-владелец вроде IntBox для каждого типа не нужно. Заголовок (header) <memory> даёт готового владельца — std::unique_ptr:
#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>:
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 чисел равны нулю. К элементам обращаются по индексу, как через указатель:
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 передают в функцию по значению:
#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); // ✗
}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 будет пуст.