S8.0 What This Teaches
std::unique_ptr- sole, non-shareable ownershipstd::shared_ptr- shared ownership with reference countingstd::weak_ptr- non-owning observer for shared objectsmake_uniqueandmake_shared- Transferring ownership with
std::move
S8.1 unique_ptr
#include <memory>
auto p = std::make_unique<int>(42); // preferred over new
std::cout << *p << "\n"; // 42
// transfer ownership
auto q = std::move(p); // p is now null; q owns the int
std::cout << (p == nullptr) << "\n"; // 1
std::cout << *q << "\n"; // 42
// q is destroyed when it goes out of scope - no delete needed
unique_ptr cannot be copied, only moved. This enforces single
ownership at compile time. Use it as the default smart pointer for any
heap-allocated object.
S8.2 shared_ptr
auto s1 = std::make_shared<std::string>("shared data");
auto s2 = s1; // both own the same string; ref count = 2
auto s3 = s1; // ref count = 3
std::cout << s1.use_count() << "\n"; // 3
s2.reset(); // release s2's ownership; ref count = 2
std::cout << s1.use_count() << "\n"; // 2
// string destroyed when last owner (s1 or s3) goes out of scope
shared_ptr carries a reference-counted control block. Use it
when ownership genuinely needs to be shared. Its overhead (atomic ref count,
two pointer dereferences) is higher than unique_ptr.
S8.3 weak_ptr
std::weak_ptr<int> weak;
{
auto strong = std::make_shared<int>(99);
weak = strong;
std::cout << weak.expired() << "\n"; // 0
if (auto locked = weak.lock()) { // promote to shared_ptr
std::cout << *locked << "\n"; // 99
}
} // strong destroyed
std::cout << weak.expired() << "\n"; // 1
weak_ptr breaks cycles that would keep objects alive forever
through shared_ptr. Call lock() to get a
temporary shared_ptr; check for null before using it.
S8.4 make_unique and make_shared
// prefer these over new:
auto p1 = std::make_unique<std::string>("hello");
auto p2 = std::make_shared<std::vector<int>>(5, 0);
// unique_ptr for arrays
auto arr = std::make_unique<int[]>(10);
arr[0] = 42;
make_unique and make_shared are exception-safe and
clearer than new. make_shared also allocates the
control block and the object in a single allocation, reducing overhead.
S8.5 Example - All Together
// SmartPointers - unique_ptr, shared_ptr, weak_ptr.
#include <iostream>
#include <memory>
#include <string>
struct Node {
std::string name;
explicit Node(std::string n) : name(std::move(n)) {
std::cout << "created " << name << "\n";
}
~Node() { std::cout << "destroyed " << name << "\n"; }
};
int main() {
std::cout << "--- unique_ptr ---\n";
{
auto p = std::make_unique<Node>("alpha");
auto q = std::move(p);
std::cout << "p null: " << (p == nullptr) << "\n";
}
std::cout << "\n--- shared_ptr ---\n";
{
auto s1 = std::make_shared<Node>("beta");
{ auto s2 = s1; std::cout << "count=" << s1.use_count() << "\n"; }
std::cout << "count=" << s1.use_count() << "\n";
}
std::cout << "\n--- weak_ptr ---\n";
std::weak_ptr<Node> w;
{
auto s = std::make_shared<Node>("gamma");
w = s;
std::cout << "expired=" << w.expired() << "\n";
}
std::cout << "expired=" << w.expired() << "\n";
return 0;
}
--- unique_ptr ---
created alpha
p null: 1
destroyed alpha
--- shared_ptr ---
created beta
count=2
count=1
destroyed beta
--- weak_ptr ---
created gamma
expired=0
destroyed gamma
expired=1
S8.6 Exercise
Exercise
- Build a singly linked list using
unique_ptr<Node>where eachNodeholds anintand aunique_ptr<Node>to the next node. Write a function that sums all values. - Create three
shared_ptrcopies of the same object and confirm the use count after each copy and each reset. - Create a circular reference using two
shared_ptrs that point at each other. Observe the memory leak. Fix it by changing one pointer toweak_ptr.
S8.7 Common Mistakes
Managing the same raw pointer with two unique_ptrs
int* raw = new int(42);
auto p1 = std::unique_ptr<int>(raw);
auto p2 = std::unique_ptr<int>(raw); // double-free: both will delete raw
make_unique. Never construct two smart pointers
from the same raw pointer.Accessing a moved-from unique_ptr
auto p = std::make_unique<int>(42);
auto q = std::move(p);
std::cout << *p; // undefined behavior: p is null after move
Creating shared_ptr from raw pointer obtained from another shared_ptr
auto s1 = std::make_shared<int>(42);
auto s2 = std::shared_ptr<int>(s1.get()); // double-free: s2 doesn't know about s1
s1 directly to get a new owner: auto s2 = s1;.S8.8 Key Terms
| Term | Meaning |
|---|---|
| unique_ptr<T> | Sole owner of a heap object; non-copyable, movable; no overhead over raw pointer |
| shared_ptr<T> | Shared ownership via reference counting; object freed when count reaches 0 |
| weak_ptr<T> | Non-owning observer; use lock() to get a temporary shared_ptr |
| make_unique<T> | Creates a unique_ptr; exception-safe, preferred over new |
| make_shared<T> | Creates a shared_ptr; single allocation for object and control block |
| use_count() | Current reference count of a shared_ptr |
| reset() | Release ownership; pointer becomes null |
| lock() | Promote weak_ptr to shared_ptr; returns null if object was destroyed |