Skip to content
Theory · Memory and Smart Pointers · lesson 6 of 12

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:

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) 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:

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

After 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:

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

A 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:

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

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.