unique_ptr vs shared_ptr Cheat Sheet
Smart pointers are how modern C++ handles ownership without naked `new`/`delete`. This snippet contrasts `std::unique_ptr` (single-owner, zero overhead) with `std::shared_ptr` (reference-counted, multi-owner) and shows when each is the right choice. The default rule: pick `unique_ptr` unless you can prove you genuinely need shared ownership.
507 views
6
#include <iostream>
#include <memory>
#include <vector>
#include <string>
struct Resource {
std::string name;
Resource(std::string n) : name(std::move(n)) {
std::cout << "acquire " << name << "\n";
}
~Resource() { std::cout << "release " << name << "\n"; }
};
int main() {
// Prefer make_unique over `unique_ptr<T>(new T(...))`: exception-safe + concise.
std::unique_ptr<Resource> a = std::make_unique<Resource>("A");
std::cout << "name=" << a->name << "\n";
// unique_ptr CANNOT be copied; only moved.
std::unique_ptr<Resource> b = std::move(a);
std::cout << "a is " << (a ? a->name : "null") << "\n";
std::cout << "b is " << b->name << "\n";
// ~Resource("A") fires when b leaves scope.
return 0;
}std::unique_ptr<T> is a single-owner smart pointer with zero runtime overhead compared to a raw pointer; the destructor calls delete automatically. Move-only semantics mean ownership transfers explicitly: after std::move(a), a is null and b owns the object. Use unique_ptr for almost every dynamic allocation in modern C++; the moment you reach for new, ask yourself whether make_unique would do. Pass unique_ptr by value to transfer ownership into a function, or pass the raw T* (or T&) when only borrowing for the duration of a call.
#include <iostream>
#include <memory>
#include <string>
struct Resource {
std::string name;
Resource(std::string n) : name(std::move(n)) {
std::cout << "acquire " << name << "\n";
}
~Resource() { std::cout << "release " << name << "\n"; }
};
void demoShared() {
std::shared_ptr<Resource> p1 = std::make_shared<Resource>("shared");
std::cout << "use_count=" << p1.use_count() << "\n"; // 1
{
std::shared_ptr<Resource> p2 = p1; // copy bumps the refcount.
std::cout << "use_count=" << p1.use_count() << "\n"; // 2
} // p2 leaves scope, refcount drops back to 1.
std::cout << "after scope use_count=" << p1.use_count() << "\n";
// Resource is freed when use_count reaches 0.
}
int main() {
demoShared();
return 0;
}std::shared_ptr<T> keeps a thread-safe reference count alongside the object; copies increment, destruction decrements, and the object is freed when the count hits zero. Always create one with std::make_shared<T>(...) rather than shared_ptr<T>(new T(...)) because make_shared can collapse the control block and the object into a single allocation. The cost is real: refcount atomics, an extra heap allocation for the control block, and the larger pointer footprint. Reach for shared_ptr only when ownership is genuinely shared (a node referenced by multiple lookup tables, a callback that outlives its creator); otherwise stick with unique_ptr.
#include <iostream>
#include <memory>
struct Node {
int value;
std::shared_ptr<Node> next;
std::weak_ptr<Node> prev; // breaks the cycle
Node(int v) : value(v) { std::cout << "node " << v << " alive\n"; }
~Node() { std::cout << "node " << value << " gone\n"; }
};
void demoWeak() {
auto a = std::make_shared<Node>(1);
auto b = std::make_shared<Node>(2);
a->next = b;
b->prev = a; // weak: does not bump a's refcount
std::cout << "a use=" << a.use_count() << " b use=" << b.use_count() << "\n";
// Both nodes are freed when this function returns.
}
int main() {
demoWeak();
return 0;
}Two shared_ptr instances that point at each other form a cycle: the refcount of each node never drops to zero, and you have a memory leak. The fix is to break the cycle with std::weak_ptr, which observes the object without contributing to the reference count. weak_ptr::lock() returns a shared_ptr if the target is still alive, or null if it has been freed. The classic pattern is parent-child trees: parent owns children with shared_ptr, children point back at parent with weak_ptr. Apply the same pattern to caches, observer lists, and any graph where multiple nodes need to reference each other.
