unique_ptr
std::unique_ptr, from the <memory> header, is the default smart pointer for owning a heap-allocated object. It expresses exclusive ownership: at any given moment, exactly one unique_ptr owns a particular object, and when that unique_ptr is destroyed, the object is automatically deleted with it. There is essentially no runtime overhead compared to a raw pointer, which is why it should be your default choice.
Creating a unique_ptr
unique_ptr_basics.cpp
CPP
#include <memory>
#include <iostream>
struct Widget {
int id;
Widget(int id) : id(id) { std::cout << "Widget " << id << " created" << std::endl; }
~Widget() { std::cout << "Widget " << id << " destroyed" << std::endl; }
};
int main() {
std::unique_ptr<Widget> w = std::make_unique<Widget>(1);
std::cout << "Using widget " << w->id << std::endl;
return 0;
} // w goes out of scope here -> destructor runs automatically -> "Widget 1 destroyed"Accessing the underlying object
A unique_ptr behaves like a raw pointer for the purposes of accessing the object it owns: use -> to call a member, and * to dereference it directly.
unique_ptr_access.cpp
CPP
auto w = std::make_unique<Widget>(2); std::cout << w->id << std::endl; // member access via -> Widget& ref = *w; // dereference to get the object itself std::cout << ref.id << std::endl; int* raw = nullptr; // just for contrast — not related to w (void)raw;
Move-only: unique_ptr cannot be copied
Because ownership must always stay exclusive, unique_ptr deletes its copy constructor and copy assignment operator. Trying to copy one is a compile error, not a runtime surprise. To transfer ownership from one unique_ptr to another, you must explicitly move it, which leaves the original empty (holding nullptr).
Copying a unique_ptr is a compile error
unique_ptr_copy_error.cpp
CPP
std::unique_ptr<Widget> a = std::make_unique<Widget>(3);
std::unique_ptr<Widget> b = a; // COMPILE ERROR: use of deleted function
// 'std::unique_ptr<Widget>::unique_ptr(const std::unique_ptr<Widget>&)'The fix is to move ownership instead of copying it, using std::move:
unique_ptr_move.cpp
CPP
#include <memory>
#include <iostream>
#include <utility>
int main() {
std::unique_ptr<Widget> a = std::make_unique<Widget>(4);
std::unique_ptr<Widget> b = std::move(a); // ownership transfers to b
if (a == nullptr) {
std::cout << "a no longer owns anything" << std::endl;
}
std::cout << "b now owns widget " << b->id << std::endl;
return 0;
}get(), release(), and reset()
Three member functions give you lower-level control when you need to interoperate with raw-pointer APIs:
.get() returns the raw pointer WITHOUT giving up ownership — the unique_ptr still owns and will still delete it.
.release() gives up ownership and returns the raw pointer — you are now responsible for deleting it yourself.
.reset() destroys the currently owned object (if any) and optionally takes ownership of a new one.
unique_ptr_get_release_reset.cpp
CPP
auto w = std::make_unique<Widget>(5); Widget* raw = w.get(); // look, but don't take ownership std::cout << raw->id << std::endl; w.reset(); // destroys widget 5 immediately, w is now empty w.reset(new Widget(6)); // takes ownership of a new widget Widget* released = w.release(); // w gives up ownership; w is now empty // you must now manually delete 'released' — it's a plain raw pointer again delete released;
Rule of thumb
Use .get() when a function needs a raw pointer just to look at the object (e.g. a C API), and only use .release() when you genuinely need to hand off ownership to code that expects to manage the raw pointer itself. Most code never needs either.