Smart Pointers
2 min read
Smart Pointers
Smart pointers are objects that behave like pointers but manage the lifetime of the heap-allocated object they point to. They eliminate memory leaks and make ownership explicit.
std::unique_ptr
unique_ptr owns an object exclusively. When the unique_ptr is destroyed, it deletes the object. Ownership can be transferred but not shared:
#include <iostream>
#include <memory>
struct Widget {
int id;
Widget(int i) : id(i) { std::cout << "Widget " << id << " created\n"; }
~Widget() { std::cout << "Widget " << id << " destroyed\n"; }
};
int main() {
auto w = std::make_unique<Widget>(1);
std::cout << w->id << "\n";
}
// Widget 1 destroyed — automatically when w goes out of scope
Always use std::make_unique<T>(args...) rather than new. It is exception-safe and cleaner.
Transferring ownership with std::move
unique_ptr cannot be copied — only moved. Moving transfers ownership:
auto a = std::make_unique<Widget>(2);
auto b = std::move(a); // b now owns Widget 2; a is null
std::cout << (a == nullptr) << "\n"; // 1
std::cout << b->id << "\n"; // 2
std::shared_ptr
shared_ptr allows multiple owners. It keeps a reference count; the object is deleted when the last shared_ptr to it is destroyed:
#include <memory>
#include <iostream>
int main() {
auto a = std::make_shared<Widget>(3);
{
auto b = a; // both own Widget 3
std::cout << a.use_count() << "\n"; // 2
}
// b destroyed — count drops to 1, Widget survives
std::cout << a.use_count() << "\n"; // 1
}
// Widget 3 destroyed when a goes out of scope
use_count() returns the current number of owners.
std::weak_ptr
weak_ptr observes a shared_ptr without contributing to the reference count. It is used to break ownership cycles that would otherwise prevent deletion:
#include <memory>
#include <iostream>
int main() {
auto shared = std::make_shared<int>(42);
std::weak_ptr<int> weak = shared;
std::cout << weak.expired() << "\n"; // 0 — still alive
if (auto locked = weak.lock()) { // temporarily elevate to shared_ptr
std::cout << *locked << "\n"; // 42
}
shared.reset();
std::cout << weak.expired() << "\n"; // 1 — object gone
}
weak.lock() returns a shared_ptr if the object is still alive, or an empty shared_ptr if it has been deleted.
Custom deleters
Both unique_ptr and shared_ptr accept a custom deleter for resources that are not freed with delete:
#include <cstdio>
#include <memory>
int main() {
// Manage a C FILE* with a unique_ptr
auto file = std::unique_ptr<FILE, decltype(&fclose)>(
fopen("data.txt", "r"),
fclose
);
if (file) {
// use file.get() to get the raw FILE*
}
}
// fclose is called automatically
Choosing the right smart pointer
| Need | Use |
|---|---|
| Single owner | std::unique_ptr |
| Shared ownership | std::shared_ptr |
Non-owning observer of a shared_ptr |
std::weak_ptr |
| Non-owning observer (always valid) | raw T* or T& |
unique_ptr has zero overhead over a raw pointer. shared_ptr has a small overhead from the reference count and control block. Use unique_ptr by default and reach for shared_ptr only when sharing is genuinely required.
Learn modern C++ through working code. Each chapter introduces one concept — variables, functions, classes, templates, smart pointers, concurrency — with a clear example, a line-by-line explanation, and notes on how it applies in real programs.
View book →