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

Passing unique_ptr to functions

The parameter type encodes the ownership intent. There are three meaningful signatures, and choosing the wrong one misleads callers about what the function does with the object's lifetime.

cpp
// C++11 β€” borrow: callee reads/writes but does not affect lifetime
void render(const Widget& w);       // prefer reference if never null
void render(Widget* w);             // raw pointer when null is a valid input

// C++11 β€” sink: callee takes permanent ownership
void store(std::unique_ptr<Widget> w);  // caller must std::move at the call site

// Avoid: suggests the callee might reset/release the pointer
void bad(std::unique_ptr<Widget>& w);   // use only when ownership transfer is conditional

Accepting const std::unique_ptr<T>& is almost always wrong β€” it accepts only unique_ptr (not raw pointers or stack objects), adds no ownership semantics, and signals intent poorly. Prefer T& or T* for borrowing.

Returning unique_ptr from factories

A factory returning unique_ptr<T> is a self-documenting ownership contract. The return type tells every caller that they receive sole responsibility for the object's lifetime, with no need for a comment or convention.

cpp
// C++14 β€” concrete factory
std::unique_ptr<Connection> open_connection(std::string_view host, uint16_t port) {
    return std::make_unique<Connection>(host, port);
}

// C++14 β€” polymorphic factory returning base type
std::unique_ptr<Codec> make_codec(std::string_view format) {
    if (format == "h264") return std::make_unique<H264Codec>();
    if (format == "av1")  return std::make_unique<AV1Codec>();
    return nullptr;                          // explicit null is fine
}

// Caller implicitly converts unique_ptr<Derived> to unique_ptr<Base>
std::unique_ptr<Codec> c = make_codec("h264");

NRVO applies to named local unique_ptr variables too. The compiler elides the move in the common case; no performance concern about "returning a large object" applies here.

unique_ptr with custom deleters

Custom deleters extend unique_ptr to manage any resource that has an acquire/release protocol. The deleter type becomes part of unique_ptr's type, so stateless deleters add zero size thanks to the empty-base optimisation.

cpp
#include <cstdio>
#include <memory>

// C++11 β€” stateless lambda, no sizeof overhead
struct FCloser { void operator()(FILE* f) const noexcept { if (f) std::fclose(f); } };
using FilePtr = std::unique_ptr<FILE, FCloser>;

FilePtr open_log(const char* path) {
    return FilePtr{std::fopen(path, "a")};
}

// C++20 β€” stateless lambda via decltype trick (common pre-C++20 pattern)
auto close_pipe = [](FILE* f) noexcept { if (f) ::pclose(f); };
using PipePtr = std::unique_ptr<FILE, decltype(close_pipe)>;

PipePtr run_cmd(const char* cmd) {
    return PipePtr{::popen(cmd, "r"), close_pipe};  // C++11
}

// C++11 β€” stateful deleter for OS resources (adds state size to sizeof)
struct MmapDeleter {
    std::size_t length;
    void operator()(void* p) const noexcept { ::munmap(p, length); }
};
std::unique_ptr<void, MmapDeleter> mapped{ptr, MmapDeleter{len}};

Avoid std::function as a deleter: it heap-allocates, is not trivially copyable, and adds at least pointer-sized overhead. A named function-object type or stateless lambda is always preferable.

unique_ptr for arrays

Use unique_ptr<T[]> when you need a heap buffer with a fixed size known at runtime, without the reallocation machinery of std::vector. The array specialisation uses delete[] and exposes operator[]; the single-object operator* and operator-> are deliberately absent.

cpp
#include <memory>
#include <cstddef>

// C++14 β€” value-initialises: all elements zeroed for trivial types
auto zeroed = std::make_unique<float[]>(1024);
zeroed[0] = 1.0f;

// C++20 β€” default-initialises: trivial elements have indeterminate value (faster for scratch buffers)
auto scratch = std::make_unique_for_overwrite<std::byte[]>(65536);  // C++20

// Pointer arithmetic is not available via unique_ptr itself β€” use .get() if needed
std::byte* raw = scratch.get();
std::fill(raw, raw + 65536, std::byte{0});

There is no make_unique<T[N]> for fixed-size stack-like arrays β€” use std::array<T, N> for those. The array form does not support custom base pointers (e.g., unique_ptr<int[4]> is not valid syntax).

Moving into containers

Because unique_ptr is move-only, all container operations that copy will fail to compile β€” this is a feature, not a limitation. The compile error is the language enforcing that you explicitly transfer ownership.

cpp
#include <vector>
#include <map>
#include <memory>

std::vector<std::unique_ptr<Widget>> widgets;

// C++11 β€” move existing unique_ptr into the container
auto w = std::make_unique<Widget>(1);        // C++14
widgets.push_back(std::move(w));             // w is now null

// C++14 β€” construct directly inside the container (preferred)
widgets.emplace_back(std::make_unique<Widget>(2));

// Moving elements out of containers
auto taken = std::move(widgets.back());      // vector slot becomes null unique_ptr
widgets.pop_back();                          // removes the (now-null) slot

// C++17 β€” maps hold unique_ptr values with structured bindings
std::map<int, std::unique_ptr<Widget>> registry;
registry.emplace(42, std::make_unique<Widget>(42));    // C++14
auto& [key, val] = *registry.find(42);                 // C++17 β€” val is unique_ptr<Widget>&

Sorting a vector of unique_ptr is fine β€” std::sort moves elements, it does not copy them. Algorithms that require CopyConstructible (e.g., std::copy) will not compile; use std::move_iterator via std::make_move_iterator if you need to transfer a range between containers.

unique_ptr vs raw pointer in interfaces

The rule of thumb: own with unique_ptr, borrow with raw pointers or references. Mixing the two intentionally β€” owning raw pointers, or borrowing via unique_ptr β€” is a common source of both bugs and misleading API surfaces.

cpp
// C++11 β€” clear ownership model
class Cache {
public:
    // Takes ownership β€” caller's unique_ptr is moved-from after this call
    void insert(std::unique_ptr<Entry> e) { entries_.push_back(std::move(e)); }

    // Returns a non-owning observer β€” caller must not delete the pointer
    const Entry* lookup(std::string_view key) const;

    // Returns ownership β€” removes entry from cache, caller now owns it
    std::unique_ptr<Entry> evict(std::string_view key);

private:
    std::vector<std::unique_ptr<Entry>> entries_;
};

A function returning T* signals "I am lending you this; do not delete it." A function returning unique_ptr<T> signals "I am giving you this; you are responsible for it." These two contracts are unambiguous when the codebase consistently uses unique_ptr for ownership.

Pimpl idiom with unique_ptr

The Pimpl (Pointer to Implementation) idiom uses unique_ptr to move a class's private data and dependencies into a separately compiled translation unit, dramatically reducing header-level coupling. The technique requires careful placement of the destructor and move operations.

cpp
// widget.h β€” only <memory> is required; no heavy dependencies visible
#pragma once
#include <memory>
#include <string>

class Widget {
public:
    explicit Widget(int id, std::string name);
    ~Widget();                           // C++11 β€” must be defined in .cpp where Impl is complete
    Widget(Widget&&) noexcept;           // C++11 β€” move-constructible
    Widget& operator=(Widget&&) noexcept;// C++11 β€” move-assignable
    // No copy β€” or provide deep-copy in .cpp if needed

    void render();
    int  id() const;

private:
    struct Impl;                         // forward declaration β€” Impl is incomplete here
    std::unique_ptr<Impl> pimpl_;        // C++11
};
cpp
// widget.cpp β€” Impl is complete here; all heavy includes stay in this TU
#include "widget.h"
#include "renderer.h"      // only in .cpp β€” not visible to widget.h consumers
#include "database.h"

struct Widget::Impl {
    int         id;
    std::string name;
    Renderer    renderer;   // expensive type hidden from header
    Database    db;
};

Widget::Widget(int id, std::string name)
    : pimpl_(std::make_unique<Impl>(id, std::move(name))) {}  // C++14

Widget::~Widget()                    = default;  // Impl complete β€” destructor compiles
Widget::Widget(Widget&&) noexcept    = default;
Widget& Widget::operator=(Widget&&) noexcept = default;

void Widget::render() { pimpl_->renderer.draw(pimpl_->id); }
int  Widget::id() const { return pimpl_->id; }

A Widget::Impl member with a non-trivial destructor (like Renderer or Database) requires the compiler-generated Widget destructor to instantiate ~unique_ptr<Impl>, which in turn requires Impl to be a complete type. Defining ~Widget() = default in the .cpp satisfies this requirement. Inlining = default in the header will fail with a cryptic "incomplete type" error.

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