Skip to content
C++
Library
since C++11
Basic

std::unique_ptr

Exclusive-ownership smart pointer that automatically frees its managed object when it goes out of scope, with zero overhead over a raw pointer.

std::unique_ptrsince C++11

A move-only smart pointer that models exclusive ownership of a heap-allocated object, automatically destroying it via its deleter when the unique_ptr is destroyed or reset.

Overview

std::unique_ptr<T> enforces a single-owner invariant at the type level: because it is non-copyable, you cannot accidentally share ownership. Transfer of ownership is explicit β€” it requires std::move. When the owning unique_ptr goes out of scope, or is assigned a new pointer, the previously managed object is destroyed through the configured deleter (by default, delete or delete[]).

With the default deleter, unique_ptr has zero size and zero runtime overhead compared to a raw pointer on all major implementations β€” the deleter is a stateless functor whose call operator is inlined. A custom stateful deleter adds size equal to the deleter's state due to the empty-base-optimisation (EBO) applied to the [T, Deleter] compressed pair.

std::auto_ptr, unique_ptr's predecessor, was deprecated in C++11 and removed in C++17. unique_ptr supersedes it entirely.

cpp
#include <memory>

Syntax

cpp
// Primary template β€” single object
template<
    class T,
    class Deleter = std::default_delete<T>
> class unique_ptr;

// Partial specialisation β€” arrays
template<
    class T,
    class Deleter
> class unique_ptr<T[], Deleter>;

Key member functions (all C++11 unless noted):

MemberDescription
get()Returns the stored raw pointer; ownership is NOT transferred
release()Releases ownership, returns the raw pointer; caller must delete
reset(p = nullptr)Destroys current object, takes ownership of p
swap(other)Swaps managed pointer and deleter with other
operator* / operator->Dereferences (single-object form only)
operator[]Element access (array form only)
operator booltrue if managing a non-null pointer
get_deleter()Returns a reference to the stored deleter

Factory functions:

cpp
// C++14 β€” prefer this over direct construction with new
template<class T, class... Args>
std::unique_ptr<T> std::make_unique(Args&&... args);

// C++20 β€” default-initialises instead of value-initialises (skips zeroing for trivial types)
template<class T>
std::unique_ptr<T> std::make_unique_for_overwrite();

Examples

Basic ownership and automatic cleanup

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

struct Connection {
    explicit Connection(std::string host) : host_(std::move(host)) {
        std::cout << "connect " << host_ << '\n';
    }
    ~Connection() { std::cout << "disconnect " << host_ << '\n'; }
    void send(std::string_view msg);
    std::string host_;
};

void process() {
    auto conn = std::make_unique<Connection>("db.prod");  // C++14
    conn->send("SELECT 1");
    // conn destroyed here β€” destructor fires automatically, no delete needed
}

new is not visible at call sites. make_unique (C++14) allocates and constructs atomically, which was also exception-safe in function argument evaluation before C++17 sequenced the evaluation of function arguments.

Transferring ownership

cpp
auto a = std::make_unique<int>(42);   // C++14
// auto b = a;           // ERROR: copy constructor is deleted
auto b = std::move(a);   // a is now null, b owns the int

// Sink functions take ownership by accepting by value
void consume(std::unique_ptr<Connection> conn);
consume(std::move(b));   // explicit transfer at the call site

Factory functions returning unique_ptr

Returning unique_ptr from a factory is the idiomatic post-C++14 pattern. The return type communicates that the caller receives sole ownership.

cpp
class Shape { public: virtual ~Shape() = default; virtual void draw() = 0; };
class Circle  : public Shape { public: void draw() override; };
class Polygon : public Shape { public: explicit Polygon(int sides); void draw() override; };

std::unique_ptr<Shape> make_shape(std::string_view kind, int param = 0) {
    if (kind == "circle")  return std::make_unique<Circle>();        // C++14
    if (kind == "polygon") return std::make_unique<Polygon>(param);  // C++14
    return nullptr;
}

// Derived-to-base conversion is implicit for unique_ptr β€” C++11
std::unique_ptr<Shape> s = make_shape("polygon", 6);
s->draw();

Array form

Use unique_ptr<T[]> for heap-allocated raw arrays β€” when std::vector is too heavy (e.g., interoperating with C APIs, or when value-initialisation overhead matters):

cpp
// C++14 β€” value-initialises (zeroes out) the array
auto zeroed = std::make_unique<int[]>(256);             // C++14

// C++20 β€” default-initialises; trivial elements are NOT zeroed
auto buf = std::make_unique_for_overwrite<std::byte[]>(65536);  // C++20

buf[0] = std::byte{0xFF};   // operator[] on unique_ptr<T[]>
// array freed when buf goes out of scope via delete[]

Custom deleters

Supply a deleter as the second template argument to manage resources other than plain heap memory:

cpp
#include <cstdio>
#include <memory>

// FILE* managed with a stateless lambda (no size overhead β€” C++20 EBO guarantee)
auto open_file(const char* path, const char* mode) {
    auto closer = [](FILE* f) { if (f) std::fclose(f); };
    return std::unique_ptr<FILE, decltype(closer)>(
        std::fopen(path, mode), closer
    );
}

// Stateful deleter for mmap regions β€” adds sizeof(length) to the pointer
struct MmapDeleter {
    std::size_t length;
    void operator()(void* p) const noexcept { ::munmap(p, length); }
};
using MmapPtr = std::unique_ptr<void, MmapDeleter>;

MmapPtr map_file(int fd, std::size_t len) {
    void* p = ::mmap(nullptr, len, PROT_READ, MAP_PRIVATE, fd, 0);
    return MmapPtr{p, MmapDeleter{len}};
}

Function pointers as deleters always add pointer-sized overhead even if pointing to the same function; prefer stateless lambdas or function objects.

Pimpl idiom

unique_ptr is the canonical tool for the Pointer-to-Implementation (Pimpl) pattern, breaking header dependencies:

cpp
// widget.h β€” minimal header, no heavy includes
#include <memory>
class Widget {
public:
    explicit Widget(int id);
    ~Widget();                          // must be defined in .cpp (Impl is incomplete here)
    Widget(Widget&&) noexcept;
    Widget& operator=(Widget&&) noexcept;

    void render();
private:
    struct Impl;                        // forward declaration only
    std::unique_ptr<Impl> pimpl_;       // C++11
};

// widget.cpp β€” Impl is a complete type here
#include "widget.h"
#include "expensive_dependency.h"

struct Widget::Impl {
    int id;
    ExpensiveThing thing;
};

Widget::Widget(int id) : pimpl_(std::make_unique<Impl>(id)) {}
Widget::~Widget() = default;            // Impl complete β€” default destructor works
Widget::Widget(Widget&&) noexcept = default;
Widget& Widget::operator=(Widget&&) noexcept = default;

The destructor must be defined (even as = default) in the .cpp where Impl is complete. An inline = default in the header sees only the forward declaration and will fail to instantiate unique_ptr's destructor.

Polymorphic containers

cpp
#include <vector>
#include <memory>
#include <algorithm>

std::vector<std::unique_ptr<Shape>> scene;
scene.push_back(std::make_unique<Circle>());     // C++14
scene.push_back(std::make_unique<Polygon>(6));   // C++14

for (const auto& s : scene) s->draw();           // virtual dispatch, unique ownership

// Ownership transfer out of the vector:
auto taken = std::move(scene[0]);  // scene[0] is now null

Best Practices

  • Default to unique_ptr over raw owning pointers. Every new not immediately stored in a smart pointer is a future memory-leak bug waiting to be triggered.
  • Prefer make_unique (C++14) to unique_ptr<T>(new T(...)). It avoids spelling T twice, removes new from call sites (useful as a code-review signal for delete), and was exception-safe in function-argument evaluation before C++17.
  • Pass raw pointer or reference to non-owning code. Functions that merely borrow an object should take T* or T&, not const unique_ptr<T>&. Passing the smart pointer implies the callee may interact with ownership (call reset, release, etc.).
  • Sink by value. Functions that take permanent ownership of an argument should accept unique_ptr<T> by value. Callers must std::move to pass it β€” the transfer of ownership is self-documenting at the call site.
  • Return by value from factories. NRVO and move semantics make this free; the caller receives an owning unique_ptr with zero copies.
  • Use make_unique_for_overwrite (C++20) for large trivially-constructible arrays that will be immediately overwritten β€” avoids the cost of zero-initialising scratch buffers.
  • Prefer unique_ptr over shared_ptr by default. unique_ptr carries no reference-counting overhead, is trivially convertible to shared_ptr if shared ownership becomes necessary later, but the reverse is not possible.

Common Pitfalls

Constructing from a pointer you don't own

cpp
int x = 42;
std::unique_ptr<int> p(&x);  // UNDEFINED BEHAVIOUR β€” calls delete on a stack address

Only construct unique_ptr from a pointer obtained via new (or the appropriate allocator for your custom deleter).

Discarding the return value of release()

cpp
auto p = std::make_unique<Widget>(1);
p.release();   // returns raw Widget* β€” immediately discarded β†’ LEAK

release() surrenders ownership without destroying the managed object. Always capture the return value; something else must take responsibility for the pointer's lifetime.

Constructing multiple unique_ptrs from the same raw pointer

cpp
Widget* raw = new Widget(1);
std::unique_ptr<Widget> a(raw);
std::unique_ptr<Widget> b(raw);  // both call delete on destruction β†’ double-free, UB

Incomplete type in Pimpl destructor

If ~MyClass() = default is inlined in the header while Impl is only forward-declared, instantiation of unique_ptr<Impl>'s destructor sees an incomplete type and the compiler emits:

cpp
error: invalid application of 'sizeof' to incomplete type 'MyClass::Impl'

Define (or = default) the destructor in the .cpp where Impl is a complete type.

Moving into containers instead of copying

unique_ptr has a deleted copy constructor. Container operations that copy will not compile:

cpp
std::vector<std::unique_ptr<int>> v;
auto p = std::make_unique<int>(7);
// v.push_back(p);          // ERROR: copy deleted
v.push_back(std::move(p));  // OK β€” transfers ownership into the vector element
v.emplace_back(std::make_unique<int>(8));  // also fine β€” construct in-place

See Also

  • std::shared_ptr β€” reference-counted shared ownership
  • std::weak_ptr β€” non-owning observer for shared_ptr-managed objects
  • std::make_shared β€” single-allocation factory for shared_ptr
  • RAII β€” the ownership pattern unique_ptr mechanises
  • Pimpl β€” compile-firewall idiom built on unique_ptr
  • Move Semantics β€” the mechanism enabling ownership transfer