Skip to content

Intermediate · Templates, smart pointers and move semantics · topic 27 of 42 · 30 min

Smart pointers 1: std::unique_ptr

Explanation

std::unique_ptr owns one heap object and deletes it automatically when the unique_ptr is destroyed (RAII for heap memory). It cannot be copied, only moved, so there is always exactly one owner. Create it with std::make_unique<T>(args), and use it as the default way to own something on the heap.

Why smart pointers?

Earlier you saw that raw new and delete cause leaks, double frees and use-after-free bugs. A smart pointer is a class that holds a pointer and applies RAII to it: when the smart pointer goes away, it deletes the object. The standard library gives two main ones in <memory>: std::unique_ptr (single owner) and std::shared_ptr (shared owners).

cpp
#include <iostream>
#include <memory>
#include <string>

struct Report {
    std::string title;
    explicit Report(std::string t) : title(std::move(t)) { std::cout << "made " << title << '\n'; }
    ~Report() { std::cout << "freed " << title << '\n'; }
};

int main() {
    auto r = std::make_unique<Report>("Sales Q1");   // std::unique_ptr<Report>
    std::cout << r->title << '\n';                   // use like a pointer
    if (true) return 0;                              // early return: still freed
}
// made Sales Q1
// Sales Q1
// freed Sales Q1
No delete anywhere, and the object is freed on every path out of main.

One owner: move, never copy

cpp
auto a = std::make_unique<int>(5);
// auto b = a;                 // error: unique_ptr cannot be copied
auto b = std::move(a);         // ownership moves to b; a is now empty (nullptr)
if (!a) std::cout << "a is empty\n";
std::cout << *b << '\n';       // 5
Copying would create two owners and a double delete, so the copy constructor is deleted. std::move transfers ownership.
Think of it like this: A unique_ptr is like the original registration papers of a car. Only one person holds them at a time. You can hand them over to a buyer (move), after which you have nothing, but you cannot photocopy them into two owners (copy).

Passing unique_ptr to functions

Function wants toParameter typeCall
just use the objectconst T& or T*use(*ptr) or use(ptr.get())
take ownershipstd::unique_ptr<T> (by value)take(std::move(ptr))
maybe replace what the caller ownsstd::unique_ptr<T>&reset(ptr) (rare)

Most functions only need to use an object, not own it. They should take a reference (or a raw pointer if "nothing" is allowed). A raw pointer that does not own anything is fine in modern C++; what you avoid is a raw pointer that must be deleted.

Useful members

  • ptr.get() returns the raw pointer without giving up ownership.
  • ptr.reset() deletes the object now and makes ptr empty; ptr.reset(new T) replaces it.
  • ptr.release() gives up ownership and returns the raw pointer; you must then delete it yourself (rarely needed).
  • if (ptr) tests whether it holds an object.
  • std::make_unique<T[]>(n) creates an array, but std::vector is usually better.

unique_ptr in containers and classes

cpp
std::vector<std::unique_ptr<Shape>> shapes;
shapes.push_back(std::make_unique<Circle>(2.0));
shapes.push_back(std::make_unique<Square>(3.0));
// the vector owns the shapes; they are deleted when the vector is destroyed

class Game {
    std::unique_ptr<Renderer> renderer_;   // Game owns its renderer; rule of zero still holds
};
A class with a unique_ptr member becomes move-only automatically, which is usually exactly right for owners.
Careful: Never create two smart pointers from the same raw pointer: int* raw = new int(1); std::unique_ptr<int> a(raw); std::unique_ptr<int> b(raw); deletes raw twice. Always use std::make_unique, so a raw pointer never appears.
Tip: unique_ptr is a "zero-overhead" abstraction: with the default deleter it is the same size as a raw pointer and just as fast. There is no reason to prefer raw owning pointers.

Need help?

Stuck on a word or an idea? Search this course, or every course, and jump straight to the explanation or a worked question.

Try:

Watch the lesson

Smart pointers 1: std::unique_ptr · 1/6

Smart pointers

Smart pointers apply RAII to heap memory, so you never write delete again.

Prefer a video by a teacher? Find free videos on “c++ unique_ptr make_unique tutorial ownership move” on YouTube ↗

In real life: Loading a level in a mobile game

A game creates a Level object on the heap when you enter a stage and frees it when you leave. Holding it in a std::unique_ptr inside the Game class means loading the next level with level_ = std::make_unique<Level>(2) automatically frees the old level first. No leaks, even after hundreds of levels in one session.

cpp
#include <iostream>
#include <memory>

struct Level {
    int number;
    explicit Level(int n) : number(n) { std::cout << "load " << n << '\n'; }
    ~Level() { std::cout << "unload " << number << '\n'; }
};

class Game {
public:
    void enter(int n) { level_ = std::make_unique<Level>(n); }   // old level freed here
private:
    std::unique_ptr<Level> level_;
};

int main() {
    Game g;
    g.enter(1);
    g.enter(2);
}
// load 1
// load 2
// unload 1
// unload 2
Assigning a new unique_ptr deletes the old object; the last one is freed when Game is destroyed.

Practice (0/4 done)

Task 1 · Question

Which line creates a unique_ptr to an int holding 10?

Task 2 · Question

What prints? auto a = std::make_unique<int>(7); auto b = std::move(a); std::cout << (a ? "a" : "-") << (b ? "b" : "-");

Task 3 · Question

Which line does NOT compile?

Task 4 · Practice

Rewrite this leaky code with std::unique_ptr so that nothing leaks on any path, and no delete appears: void process(bool fail) { Buffer* b = new Buffer(1024); if (fail) return; use(*b); delete b; }

Quick quiz

Question 1

1.When is the object owned by a unique_ptr deleted?

Question 2

2.How do you pass ownership of a unique_ptr into a function sink(std::unique_ptr<T> p)?

Question 3

3.What does p.get() return?

Question 4

4.What is the size overhead of std::unique_ptr<T> with the default deleter?

Read more (free)

Open a question to see the answer and why. The first ones are this topic's own quiz, so try the quiz above before you peek.

When is the object owned by a unique_ptr deleted?

Answer: When the unique_ptr is destroyed or reset

The destructor of unique_ptr deletes the object.

How do you pass ownership of a unique_ptr into a function sink(std::unique_ptr<T> p)?

Answer: sink(std::move(p))

Ownership must be moved explicitly.

What does p.get() return?

Answer: The raw pointer, while p keeps ownership

get() is for passing to code that only uses the object.

What is the size overhead of std::unique_ptr<T> with the default deleter?

Answer: Usually none: same size as a raw pointer

It is a zero-overhead wrapper in practice.

Why prefer std::make_shared over std::shared_ptr<T>(new T)?Related

Answer: One allocation for object and control block, and no raw new in your code

make_shared is faster and safer.

From Smart pointers 2: std::shared_ptr and std::weak_ptr

Which pair makes a deep copy automatically when you copy the object?Related

Answer: A std::vector member

std::vector copies all its elements into new memory.

From Copying objects: the rule of three, five and zero

Why should you avoid creating an RAII object with new?Related

Answer: Its destructor only runs when you delete it, so the automatic cleanup is lost

RAII relies on automatic lifetime. A heap object lives until someone deletes it.

From RAII: let objects clean up for you

Which memory is cleaned up automatically when a function returns?Related

Answer: The function's local variables on the stack

Stack variables are destroyed at the end of their scope; heap memory stays until deleted.

From Pointers, the stack and the heap

Pass the quiz (60% or more) or mark this topic as done when you are ready.

Your progress is saved on this device only.

Next steps

  1. 2 more topics in “Templates, smart pointers and move semantics” before its exam: Smart pointers 2: std::shared_ptr and std::weak_ptr, Move semantics: steal instead of copy.
  2. Module 7 exam: Templates, smart pointers and move semantics · 12 questions · 25 min, without help. 4 topics of this module still to finish first.
  3. Then the next module, Modern C++: lambdas, C++17 and C++20 essentials. 42 topics left in the course before the final exam.

← Previous: Templates: code for any type