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++11A 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.
Header
#include <memory>Syntax
// 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):
| Member | Description |
|---|---|
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 bool | true if managing a non-null pointer |
get_deleter() | Returns a reference to the stored deleter |
Factory functions:
// 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
#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
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 siteFactory 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.
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):
// 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:
#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:
// 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
#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 nullBest Practices
- Default to
unique_ptrover raw owning pointers. Everynewnot immediately stored in a smart pointer is a future memory-leak bug waiting to be triggered. - Prefer
make_unique(C++14) tounique_ptr<T>(new T(...)). It avoids spellingTtwice, removesnewfrom call sites (useful as a code-review signal fordelete), 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*orT&, notconst unique_ptr<T>&. Passing the smart pointer implies the callee may interact with ownership (callreset,release, etc.). - Sink by value. Functions that take permanent ownership of an argument should accept
unique_ptr<T>by value. Callers muststd::moveto 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_ptrwith 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_ptrovershared_ptrby default.unique_ptrcarries no reference-counting overhead, is trivially convertible toshared_ptrif shared ownership becomes necessary later, but the reverse is not possible.
Common Pitfalls
Constructing from a pointer you don't own
int x = 42;
std::unique_ptr<int> p(&x); // UNDEFINED BEHAVIOUR β calls delete on a stack addressOnly construct unique_ptr from a pointer obtained via new (or the appropriate allocator for your custom deleter).
Discarding the return value of release()
auto p = std::make_unique<Widget>(1);
p.release(); // returns raw Widget* β immediately discarded β LEAKrelease() 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
Widget* raw = new Widget(1);
std::unique_ptr<Widget> a(raw);
std::unique_ptr<Widget> b(raw); // both call delete on destruction β double-free, UBIncomplete 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:
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:
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-placePassing 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.
// 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 conditionalAccepting 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.
// 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.
#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.
#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.
#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.
// 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.
// 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
};// 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 ownershipstd::weak_ptrβ non-owning observer forshared_ptr-managed objectsstd::make_sharedβ single-allocation factory forshared_ptr- RAII β the ownership pattern
unique_ptrmechanises - Pimpl β compile-firewall idiom built on
unique_ptr - Move Semantics β the mechanism enabling ownership transfer