std::unique_ptr and std::move
There is no need to write an owner class like IntBox for every type. The <memory> header provides a ready-made owner, 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) creates an int on the heap and returns a std::unique_ptr<int> that owns it. The number is read through *square, as through a plain pointer. When square is destroyed, its destructor deletes the number, whatever way the function exits. Owner classes that behave like pointers are called smart pointers.
A unique_ptr can also take over an address from new: std::unique_ptr<int> p(new int{5});. But with std::make_unique, the code has no new left, and so no place where a matching delete must be found.
One owner
An object owned by a std::unique_ptr has exactly one owner. That is why a unique_ptr cannot be copied: its copy constructor is deleted, as in IntBox with = delete. Ownership can be passed to another unique_ptr instead, with std::move from the <utility> header:
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 1After this, b owns the number, and a is empty and equal to nullptr. Such a transfer is called a move: the number itself stays in place, and only its owner changes. Dereferencing a after the move is the same as dereferencing nullptr, which is undefined behavior.
A function returns a unique_ptr by value, and it needs no std::move for that. A local variable in return is moved automatically: it is destroyed after the function exits anyway.
An owned array
std::unique_ptr<int[]> owns an array and deletes it with delete[]. Such an array is created by std::make_unique<int[]>(n), and all its n numbers are zero. Elements are accessed by index, as through a pointer:
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'; // 4A unique_ptr does not store the array's size, so a function gets the size as a separate parameter.
Common mistake
A unique_ptr is passed to a function by value:
#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>'A by-value parameter is a copy of the argument, and a unique_ptr cannot be copied. In the course exercises, the same message reads differently: call to implicitly-deleted copy constructor of 'std::unique_ptr<int>'.
If the function only reads the number, declare the parameter as const std::unique_ptr<int>& p. If it must take over ownership, call show(std::move(a)), and a is empty after the call.