Smart Pointers: Automated Memory Management
Raw pointers require manual memory management with new/delete, leading to leaks, double-deletes, and dangling pointers. Smart pointers from <memory> automate lifetime management through RAII.
unique_ptr: Exclusive Ownership
unique_ptr<T> represents exclusive ownership: exactly one owner, non-copyable but movable. The pointed-to object is destroyed when the unique_ptr is destroyed.
#include <memory>
std::unique_ptr<int> p1(new int(42));
// std::unique_ptr<int> p2 = p1; // Error: cannot copy
std::unique_ptr<int> p2 = std::move(p1); // OK: transfer ownership
// p1 is now nullptr
auto p3 = std::make_unique<int>(100); // Preferred (C++14)
Why make_unique?
// Problematic:
process(std::unique_ptr<T>(new T), might_throw());
// If might_throw() throws after new but before unique_ptr construction, leak!
// Safe:
process(std::make_unique<T>(), might_throw());
// make_unique is atomic: allocation and unique_ptr construction happen together
unique_ptr for Arrays
std::unique_ptr<int[]> arr(new int[10]);
arr[0] = 5; // Operator[] available for array version
// Prefer std::vector or std::array
std::vector<int> vec(10); // Better
Custom Deleters
struct FileCloser {
void operator()(FILE* fp) const {
if (fp) fclose(fp);
}
};
std::unique_ptr<FILE, FileCloser> file(fopen("data.txt", "r"));
// File automatically closed when unique_ptr destroyed
shared_ptr: Shared Ownership
shared_ptr<T> uses reference counting: the object is destroyed when the last shared_ptr owning it is destroyed. Copyable and movable.
std::shared_ptr<int> p1 = std::make_shared<int>(42);
std::shared_ptr<int> p2 = p1; // Both own the object
// Reference count: 2
p1.reset(); // p1 releases ownership, count = 1
// p2 still valid, object alive
Control Block Architecture
Each shared_ptr has two pointers:
- Pointer to the managed object
- Pointer to the control block (reference counts, deleter)
Creating shared_ptr
// Preferred: single allocation for object + control block auto p1 = <int>(42); // Avoid: two allocations std::shared_ptr<int> p2(new int(42));
Cyclic References Problem
struct Node {
std::shared_ptr<Node> next;
};
std::shared_ptr<Node> a = std::make_shared<Node>();
std::shared_ptr<Node> b = std::make_shared<Node>();
a->next = b; // a owns b, count = 2
b->next = a; // b owns a, count = 2
// Neither can be destroyed: memory leak!
weak_ptr: Breaking Cycles
weak_ptr<T> observes a shared_ptr without affecting reference count. Used to break cycles and check if object still exists.
struct Node {
std::shared_ptr<Node> next;
std::weak_ptr<Node> prev; // Doesn't own
};
std::shared_ptr<Node> a = std::make_shared<Node>();
std::shared_ptr<Node> b = std::make_shared<Node>();
a->next = b;
b->prev = a; // Weak reference: no cycle
Using weak_ptr
std::shared_ptr<int> sp = std::make_shared<int>(42);
std::weak_ptr<int> wp = sp;
if (auto locked = wp.lock()) { // Attempt to get shared_ptr
std::cout << *locked << '\\n'; // Object still exists
} else {
std::cout << "Object destroyed\\n";
}
Comparison: unique_ptr vs shared_ptr
| Aspect | unique_ptr | shared_ptr |
|---|---|---|
| Ownership | Exclusive | Shared |
| Copyable | No | Yes |
| Size | Size of pointer | 2 pointers |
| Overhead | Zero | Reference counting (atomic for thread safety) |
| Use When | Clear single owner | Unclear ownership, need sharing |
Rule of Thumb: Default to unique_ptr. Use shared_ptr only when ownership truly must be shared.
Returning Smart Pointers from Functions
// Return unique_ptr when transferring ownership
std::unique_ptr<Widget> createWidget() {
return std::make_unique<Widget>();
}
auto w = createWidget(); // Ownership transferred via move
// Prefer returning by value for copyable types
Widget createWidget2() {
return Widget{}; // Copy elision / move
}
Smart Pointers and Polymorphism
class Base {
public:
virtual ~Base() = default;
};
class Derived : public Base {};
std::unique_ptr<Base> p = std::make_unique<Derived>();
// Polymorphism works: Derived destructor called when p destroyed
Common Pitfalls
Constructing shared_ptr from Same Raw Pointer Twice
int* raw = new int(42);
std::shared_ptr<int> p1(raw);
std::shared_ptr<int> p2(raw); // DISASTER: double-delete!
// Each shared_ptr has independent control block
Never create multiple shared_ptr from the same raw pointer. Use make_shared or copy existing shared_ptr.
What happens when you copy a unique_ptr?
Interactive Lab
Waiting for signal...