Smart Pointers
Automatic memory management with unique_ptr, shared_ptr, and weak_ptr
The Raw Pointer Problem
// Raw pointer issues:
// 1. Ownership unclear
// 2. Manual delete required
// 3. Exception safety problems
// 4. Easy to leak or double-free
void process() {
Resource* r = new Resource();
if (some_condition()) {
return; // Leak! delete never called
}
if (other_condition()) {
throw std::runtime_error("error"); // Leak!
}
delete r; // Easy to forget
}std::unique_ptr
Exclusive ownership. Lightweight, zero overhead. The go-to smart pointer.
#include <memory>
// Creating unique_ptr
auto ptr = std::make_unique<Resource>(); // Preferred
std::unique_ptr<Resource> ptr2(new Resource()); // Avoid direct new
// Automatic cleanup
void process() {
auto r = std::make_unique<Resource>();
if (some_condition()) {
return; // Resource freed automatically
}
throw std::runtime_error("error"); // Resource freed automatically
} // Resource freed when ptr goes out of scope
// Transfer ownership
auto ptr2 = std::move(ptr); // ptr is now empty
// ptr->doSomething(); // ERROR: nullptr dereference
// Pass to functions
void use_resource(const Resource* r); // Borrow
void take_resource(std::unique_ptr<Resource> r); // Take ownership
void maybe_modify(std::unique_ptr<Resource>& r); // Might modifystd::shared_ptr
Shared ownership with reference counting. Use when multiple owners need the object.
// Creating shared_ptr
auto ptr = std::make_shared<Resource>(); // Preferred (single allocation)
std::shared_ptr<Resource> ptr2(new Resource()); // Two allocations
// Shared ownership
{
auto ptr1 = std::make_shared<Resource>();
{
auto ptr2 = ptr1; // Reference count = 2
{
auto ptr3 = ptr1; // Reference count = 3
} // Reference count = 2
} // Reference count = 1
} // Reference count = 0, Resource destroyed
// Custom deleter
auto file = std::shared_ptr<FILE>(
fopen("data.txt", "r"),
[](FILE* f) { if (f) fclose(f); }
);std::weak_ptr
Non-owning "observer" pointer. Breaks circular references.
// Problem: Circular reference causes leak
struct Node {
std::shared_ptr<Node> next;
};
auto a = std::make_shared<Node>();
auto b = std::make_shared<Node>();
a->next = b;
b->next = a; // Memory leak! Neither will be destroyed
// Solution: weak_ptr
struct Node {
std::weak_ptr<Node> next; // Doesn't keep next alive
void process() {
if (auto shared = next.lock()) { // Convert to shared_ptr
// shared is valid, use it
} else {
// Node was destroyed
}
}
};Comparison Table
| Feature | unique_ptr | shared_ptr | weak_ptr |
|---|---|---|---|
| Ownership | Exclusive | Shared | None (observes) |
| Copyable | No (move only) | Yes | Yes |
| Overhead | None | Reference count (atomic) | Minimal |
| Use for | Single ownership | Shared ownership | Breaking cycles |
Factory Functions
// std::make_unique (C++14)
auto ptr = std::make_unique<MyClass>(arg1, arg2);
// Benefits:
// - Exception safe
// - Single allocation
// - Cleaner syntax
// std::make_shared (C++11)
auto ptr = std::make_shared<MyClass>(arg1, arg2);
// Benefits:
// - Single allocation (object + control block together)
// - Better cache locality
// - Exception safe
// Avoid direct new:
std::unique_ptr<MyClass> bad(new MyClass()); // Exception unsafe
std::shared_ptr<MyClass> bad2(new MyClass()); // Two allocationsBest Practices
- ✓Default to std::unique_ptr for ownership
- ✓Use std::make_unique and std::make_shared
- ✓Pass unique_ptr by value to transfer ownership
- ✓Pass shared_ptr by const reference if not taking ownership
- ✓Use weak_ptr to break circular references
- ✗Don't use new/delete directly
- ✗Don't create shared_ptr from raw pointer multiple times