cpp-unique-ptr

What is std::unique_ptr and How to Use It in C++

  • 4 min

A std::unique_ptr is a smart pointer with exclusive ownership of the object it points to.

It’s the smart pointer you should use by default when you need dynamic memory. It’s lightweight, fast, and semantically very clear: it represents exclusive ownership of a resource.

What is std::unique_ptr?

std::unique_ptr is a wrapper that ensures only one unique owner exists for the object it points to.

Imagine you have a key to a safe.

  • Only you have the key.
  • If you want someone else to have access, you have to give them your key.
  • You are left without it. You cannot duplicate it.

This has immediate implications in code:

  1. It cannot be copied: If you try to copy a unique_ptr, the compiler will give you an error.
  2. It can be moved: You can transfer ownership to another unique_ptr, but the original will become empty (nullptr).
  3. Automatic destruction: When the unique_ptr goes out of scope, it destroys the object it owns.

With the default deleter, std::unique_ptr usually has the same size as a raw pointer and doesn’t need reference counters or locks. A custom stateful deleter can increase its size.

Creating a unique_ptr

Just like with shared pointers, there’s a helper function to create them: std::make_unique. This function arrived in C++14 (a bit later than C++11), but it’s the standard today.

#include <memory>
#include <iostream>

int main() {
    // CORRECT WAY (C++14 onwards)
    std::unique_ptr<int> number = std::make_unique<int>(42);

    // Accessing the value (just like a normal pointer)
    std::cout << "Value: " << *number << "\n";
    
    // We can reset it manually if we want (deletes the memory)
    number.reset(); 
    
    if (number == nullptr) {
        std::cout << "The pointer is empty\n";
    }
}
Copied!

Exclusive ownership: copy vs. move

This is the point where many people get confused at first. Let’s see it with code.

Attempted copy

std::unique_ptr<int> p1 = std::make_unique<int>(10);

// COMPILATION ERROR!
// std::unique_ptr<int> p2 = p1; 
Copied!

The compiler forbids this because if it allowed two unique pointers to the same location, both would try to delete the memory when going out of scope. Result: double free and program crash.

Ownership transfer

If we want p2 to manage the resource, we have to move it explicitly using std::move.

std::unique_ptr<int> p1 = std::make_unique<int>(10);

// CORRECT: Transfer ownership
std::unique_ptr<int> p2 = std::move(p1);

if (!p1) {
    std::cout << "p1 is now nullptr (it has lost ownership)\n";
}

std::cout << "p2 is the new owner: " << *p2 << "\n";
Copied!

After the move, p1 is in a valid but empty state (pointing to nullptr). p2 is now solely responsible for deleting the memory.

Passing to functions: By value or reference?

This is a very common question: “How do I pass a unique_ptr to a function?” The answer depends on what you want to do with ownership.

1. I just want to use the object (Observe)

If the function only needs to read or modify the object, but not manage it, pass a reference to the object or a raw pointer (using .get()). Don’t move the unique_ptr.

// Option A: Const reference (Recommended for read-only)
void print(const MyClass& obj) {
    obj.display();
}

// Option B: Raw pointer (If it can be nullptr)
void process(MyClass* obj) {
    if(obj) obj->calculate();
}

int main() {
    auto ptr = std::make_unique<MyClass>();
    
    print(*ptr);      // We pass the object, ptr remains the owner
    process(ptr.get()); // We pass a raw pointer, ptr remains the owner
}
Copied!

2. I want to transfer responsibility (Transfer)

If the function is going to keep the object (for example, add it to an internal list), then pass the unique_ptr by value and use std::move when calling.

void saveToDatabase(std::unique_ptr<MyClass> obj) {
    // Now 'obj' is owned by this function.
    // It will be destroyed when the function ends (or moved elsewhere).
}

int main() {
    auto ptr = std::make_unique<MyClass>();
    
    saveToDatabase(std::move(ptr));
    
    // HERE ptr IS NO LONGER VALID. Don't use it.
}
Copied!

Use in collections

Thanks to move semantics, unique_ptr’s are perfect for storing in std::vector or other containers. This was very difficult in C++98, but in Modern C++ it’s trivial.

std::vector<std::unique_ptr<Enemy>> enemies;

enemies.push_back(std::make_unique<Enemy>("Level 1"));
enemies.push_back(std::make_unique<Enemy>("Level 2"));

// When the vector is cleared, all enemies are automatically cleaned up
// enemies.clear(); // Destroys all objects
Copied!

Using std::vector<std::unique_ptr<T>> is the standard way to handle polymorphic collections (a list of objects from different child classes) in C++.