Giao diện
👁️ weak_ptr — Breaking Cycles
std::weak_ptr là observer — không sở hữu object nhưng có thể kiểm tra nó còn tồn tại hay không. Analogy: Người quan sát
┌─────────────────────────────────────────────────────────────────┐
│ OBSERVER ANALOGY │
├─────────────────────────────────────────────────────────────────┤
│ │
│ shared_ptr = Chủ sở hữu căn nhà │
│ weak_ptr = Người quan sát (không sở hữu) │
│ │
│ • Quan sát có thể nhìn nhà (nếu còn tồn tại) │
│ • Quan sát KHÔNG giữ nhà lại │
│ • Khi tất cả chủ sở hữu bỏ đi → Nhà bị phá │
│ • Quan sát phải kiểm tra: "Nhà còn không?" trước khi vào │
│ │
│ ┌─────────┐ ┌──────────────────┐ │
│ │ Owner │────────►│ Shared Resource │ │
│ └─────────┘ │ ref_count: 1 │ │
│ │ weak_count: 2 │ │
│ ┌─────────┐ └──────────────────┘ │
│ │Observer │- - - - - - - - ↗ (non-owning) │
│ │(weak_ptr)│ │
│ └─────────┘ │
│ │
└─────────────────────────────────────────────────────────────────┘The Circular Reference Problem
cpp
#include <memory>
struct Node {
std::shared_ptr<Node> next;
~Node() { std::cout << "Node destroyed\n"; }
};
int main() {
auto a = std::make_shared<Node>();
auto b = std::make_shared<Node>();
a->next = b; // a → b
b->next = a; // b → a (CYCLE!)
std::cout << "a.use_count(): " << a.use_count() << "\n"; // 2
std::cout << "b.use_count(): " << b.use_count() << "\n"; // 2
} // Exit: No "Node destroyed" printed! LEAK! CIRCULAR REFERENCE
──────────────────
a b
┌───────┐ ┌───────┐
│ ──────────────► │
│ │ │ │
│ ◄────────────── │
└───────┘ └───────┘
use_count=2 use_count=2
When main exits:
a goes out of scope → a.use_count = 1 (b still points to a)
b goes out of scope → b.use_count = 1 (a still points to b)
Neither reaches 0 → MEMORY LEAK!Solution: weak_ptr
cpp
#include <memory>
struct Node {
std::shared_ptr<Node> next;
std::weak_ptr<Node> prev; // ← weak_ptr breaks cycle!
~Node() { std::cout << "Node destroyed\n"; }
};
int main() {
auto a = std::make_shared<Node>();
auto b = std::make_shared<Node>();
a->next = b; // a → b (shared)
b->prev = a; // b ← a (weak, doesn't count!)
std::cout << "a.use_count(): " << a.use_count() << "\n"; // 1
std::cout << "b.use_count(): " << b.use_count() << "\n"; // 2
} // Both nodes destroyed correctly!Output:
a.use_count(): 1
b.use_count(): 2
Node destroyed
Node destroyedBasic Usage
cpp
#include <memory>
int main() {
std::weak_ptr<int> weak;
{
auto shared = std::make_shared<int>(42);
weak = shared; // weak observes shared
std::cout << "expired: " << weak.expired() << "\n"; // 0 (false)
// To use weak_ptr, must lock() to get shared_ptr
if (auto sp = weak.lock()) {
std::cout << *sp << "\n"; // 42
}
} // shared destroyed
std::cout << "expired: " << weak.expired() << "\n"; // 1 (true)
if (auto sp = weak.lock()) {
// This branch won't execute
} else {
std::cout << "Object is gone\n";
}
}lock() Method
lock() returns a shared_ptr:
- If object exists: returns valid shared_ptr
- If object destroyed: returns empty shared_ptr
cpp
std::weak_ptr<Widget> cache;
void useCache() {
// Try to get shared_ptr from weak_ptr
if (std::shared_ptr<Widget> sp = cache.lock()) {
// Object still alive, use it
sp->doSomething();
} else {
// Object was deleted, need to recreate
auto newWidget = std::make_shared<Widget>();
cache = newWidget;
newWidget->doSomething();
}
}Real Example: Tree Structure
cpp
#include <memory>
#include <vector>
struct TreeNode {
int value;
std::weak_ptr<TreeNode> parent; // Parent = weak (prevent cycle)
std::vector<std::shared_ptr<TreeNode>> children; // Children = shared
explicit TreeNode(int v) : value(v) {}
};
std::shared_ptr<TreeNode> createTree() {
auto root = std::make_shared<TreeNode>(1);
auto child1 = std::make_shared<TreeNode>(2);
auto child2 = std::make_shared<TreeNode>(3);
child1->parent = root; // weak reference
child2->parent = root;
root->children.push_back(child1); // shared reference
root->children.push_back(child2);
return root;
}
// When root destroyed, all children destroyed too
// No memory leak!Real Example: Observer Pattern
cpp
#include <memory>
#include <vector>
#include <algorithm>
class Subject {
std::vector<std::weak_ptr<Observer>> observers_;
public:
void addObserver(std::shared_ptr<Observer> obs) {
observers_.push_back(obs); // Store as weak
}
void notify() {
// Clean up expired observers and notify active ones
observers_.erase(
std::remove_if(observers_.begin(), observers_.end(),
[](const std::weak_ptr<Observer>& wp) {
return wp.expired();
}),
observers_.end()
);
for (auto& wp : observers_) {
if (auto sp = wp.lock()) {
sp->onNotify();
}
}
}
};Real Example: Cache
cpp
#include <memory>
#include <unordered_map>
class ResourceCache {
std::unordered_map<std::string, std::weak_ptr<Resource>> cache_;
public:
std::shared_ptr<Resource> get(const std::string& key) {
// Try to get from cache
auto it = cache_.find(key);
if (it != cache_.end()) {
if (auto sp = it->second.lock()) {
return sp; // Cache hit!
}
// Resource was deleted, remove from cache
cache_.erase(it);
}
// Cache miss - load resource
auto resource = loadResource(key);
cache_[key] = resource; // Store as weak
return resource;
}
};When to Use weak_ptr
| Scenario | Use |
|---|---|
| Breaking cycles | Parent-child, doubly-linked |
| Caching | Resource pools, memoization |
| Observer pattern | Non-owning subscribers |
| Optional reference | May or may not exist |
📚 Tổng kết
| Feature | Description |
|---|---|
| Ownership | None (observer only) |
| Extends lifetime | ❌ No |
| Access object | Must call lock() |
| Check validity | expired() or lock() |
| Main use | Breaking cycles |
➡️ Tiếp theo
Khi tạo class quản lý resource, cần biết Rule of 5:
Rule of 5 → — Special member functions.