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-placeSee 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