std::unique_ptr

std::unique_ptr<T> owns one T and deletes it when the pointer dies. It is the smallest smart pointer: the size of a raw pointer, no reference count, and no copy, because two owners of one object is exactly the bug it prevents.

It replaces the raw new of lesson 089 in almost every case. This lesson uses it for a single object, passes it into a function, returns it from a factory and stores a derived object behind a base pointer.

A unique_ptr also has a deleter, which defaults to delete and can be replaced, as the next lesson on custom deleters shows. The default is what makes the type a drop-in replacement for a raw owning pointer.

The type is also cheap to return: the move is a pointer copy, and the compiler elides even that in most cases, so a factory that returns a unique_ptr costs no allocation beyond the object itself.

make_unique

std::make_unique<point>(3, 4) allocates a point, passes the arguments to its constructor and returns a unique_ptr that owns it. The pointer reads its object with -> and *, exactly like a raw pointer.

The program prints the fields through the arrow and asks whether the pointer is null. The delete happens in the destructor, so there is no line for it and no way to forget it.

make_unique is preferred to unique_ptr<point>(new point{...}) because the allocation and the ownership are one expression. With the separate form, an exception between the two could leak the raw pointer.

#include <iostream> #include <memory> struct point { point(int x, int y) : x(x), y(y) {} int x; int y; }; int main() { auto p = std::make_unique<point>(3, 4); std::cout << p->x << ' ' << p->y << ' ' << (p != nullptr) << '\n'; }

g++ -std=c++23 -Wall -Wextra first.cpp -o first
./first

3 4 1

A unique_ptr moves, never copies

A unique_ptr cannot be copied, so a function that takes one by value takes ownership. The caller writes std::move and the source pointer becomes null.

The program passes a pointer into consume() and prints the size inside and the null state after. The object is destroyed when the parameter goes out of scope, at the end of the function.

That is the transfer of lesson 032 in the standard library. A function that only reads the object takes const T& or a raw pointer, not the unique_ptr itself.

#include <iostream> #include <memory> #include <utility> struct point { point(int x, int y) : x(x), y(y) {} int x; int y; }; void consume(std::unique_ptr<point> p) { std::cout << p->x << ' '; } int main() { auto p = std::make_unique<point>(7, 8); consume(std::move(p)); std::cout << (p == nullptr) << '\n'; }

g++ -std=c++23 -Wall -Wextra move.cpp -o move
./move

7 1

A factory returns one

A function that creates an object returns a unique_ptr and transfers the ownership to the caller. The return value is moved or elided, so no copy is attempted and no reference count is involved.

The program's make_point returns a pointer and main stores it in auto. The same shape is used by every factory in a library that does not want to expose its constructors.

The return type is the documentation: a unique_ptr says the caller is now the only owner. A raw pointer in the same position would leave the question open, which is why the factory style changed after C++11.

#include <iostream> #include <memory> struct point { point(int x, int y) : x(x), y(y) {} int x; int y; }; std::unique_ptr<point> make_point(int x, int y) { return std::make_unique<point>(x, y); } int main() { auto p = make_point(1, 2); std::cout << p->x << ' ' << p->y << '\n'; }

g++ -std=c++23 -Wall -Wextra factory.cpp -o factory
./factory

1 2

A base pointer with ownership

A unique_ptr<shape> can own a square, because the derived pointer converts to the base pointer and the virtual destructor of lesson 035 runs the right one.

The program stores a square behind a unique_ptr<shape> and calls area() through it. The call is virtual, and the delete at the end is the base's virtual destructor.

This is the modern replacement for shape* p = new square; ... delete p;. The ownership is in the type, so a reader does not have to search for the delete.

#include <iostream> #include <memory> class shape { public: virtual ~shape() = default; virtual int area() const = 0; }; class square : public shape { public: explicit square(int side) : side_(side) {} int area() const override { return side_ * side_; } private: int side_; }; int main() { std::unique_ptr<shape> s = std::make_unique<square>(4); std::cout << s->area() << '\n'; }

g++ -std=c++23 -Wall -Wextra polymorphism.cpp -o polymorphism
./polymorphism

16

The copy that does not exist

Copying a unique_ptr is a compile error, and the message names the deleted copy constructor. That error is the feature: it makes a second owner impossible to create by accident.

The program assigns one unique_ptr to another and the compiler refuses. The fix is std::move when the ownership really moves, or a reference when the callee only reads.

The same message appears when a unique_ptr is stored in a container that copies its elements, or captured by value in a lambda that is copied. Use std::move into the container, or a shared_ptr when several owners are really needed.

#include <memory> int main() { auto first = std::make_unique<int>(1); auto second = first; return *second; }

g++ -std=c++23 -Wall -Wextra copy.cpp -o copy

copy.cpp: In function 'int main()':
copy.cpp:6:19: error: use of deleted function 'std::unique_ptr<_Tp, _Dp>::unique_ptr(const std::unique_ptr<_Tp, _Dp>&) [with _Tp = int; _Dp = std::default_delete<int>]'
6 | auto second = first;
| ^~~~~
In file included from /usr/include/c++/15.2.0/memory:80,
from copy.cpp:1:
/usr/include/c++/15.2.0/bits/unique_ptr.h:525:7: note: declared here
525 | unique_ptr(const unique_ptr&) = delete;
| ^~~~~~~~~~
copy.cpp:6:19: note: use '-fdiagnostics-all-candidates' to display considered candidates
6 | auto second = first;
| ^~~~~

Exercises

  1. Write a function that takes a unique_ptr by const reference and prints the object without taking ownership.
  2. Make the factory return a unique_ptr and store a square and a rectangle in a vector.
  3. Replace the raw new in one program of lesson 089 with make_unique and remove the delete.

Questions

A new set of questions in a new order every time. Choose one answer for each, then check.

  1. What does std::make_unique<point>(3, 4) do?

    Answer

    Allocates a point and passes 3 and 4 to its constructor

    make_unique forwards the arguments to the constructor and returns the owning pointer.

  2. What happens when a unique_ptr is copied?

    Answer

    The compiler rejects it: the copy constructor is deleted

    Exclusive ownership means no copies; the deleted copy constructor turns the mistake into a compile error.

  3. What does this program print? auto p = std::make_unique<int>(7); auto q = std::move(p); std::cout << *q << ' ' << (p == nullptr);

    Answer

    7 1

    The move transfers the object, so q holds 7 and the source is null.

  4. What is the size of a unique_ptr compared with a raw pointer on this build?

    Answer

    The same, one pointer

    A unique_ptr stores only the pointer and a deleter that is empty for the default one.

  5. How does a function take a unique_ptr without taking ownership?

    Answer

    By const reference

    A const reference reads the object and leaves the ownership with the caller.

  6. What does this factory return? std::unique_ptr<point> make() { return std::make_unique<point>(1, 2); }

    Answer

    A pointer whose ownership passes to the caller

    The returned unique_ptr is moved out of the function and owns the object from then on.

  7. Why must the base class of a unique_ptr<base> have a virtual destructor?

    Answer

    So that deleting through the base runs the derived destructor

    The delete happens through the base type, and only a virtual destructor reaches the derived one.

  8. What does this program print? auto p = std::make_unique<int>(5); std::cout << *p << ' ' << (p != nullptr);

    Answer

    5 1

    The object holds 5 and the pointer is not null, so the two prints are 5 and 1.

  9. Which expression releases the object held by a unique_ptr early?

    Answer

    p.reset()

    reset() destroys the object and leaves the pointer null; assigning nullptr does the same, but reset is the named operation.

  10. When is a shared_ptr the better choice than a unique_ptr?

    Answer

    When several parts of the program must own it

    shared_ptr exists for shared ownership; unique_ptr is the default when one owner is enough.

The code on this page was built with g++ 15.2.0 and run on Alpine Linux 3.24.3 (x86_64) in a Docker container. The programs whose section shows a compiler error were compiled until that error and were not run.

Article author: Arthur Isaev