Giao diện
🤝 shared_ptr — Shared Ownership
std::shared_ptr cho phép nhiều owners cùng sở hữu một object. Object tự động bị hủy khi owner cuối cùng biến mất. Analogy: Sổ tiết kiệm chung
┌─────────────────────────────────────────────────────────────────┐
│ SHARED BANK ACCOUNT │
├─────────────────────────────────────────────────────────────────┤
│ │
│ shared_ptr = Sổ tiết kiệm chung của nhóm │
│ │
│ • Nhiều người đồng sở hữu cùng tài khoản │
│ • Ngân hàng đếm số người sở hữu (reference count) │
│ • Mỗi người rời đi → count giảm │
│ • Người cuối cùng rời → Tài khoản đóng (delete) │
│ │
│ ┌─────────┐ │
│ │ Owner A │──┐ │
│ └─────────┘ │ ┌──────────────────┐ │
│ ├────────►│ Shared Resource │ │
│ ┌─────────┐ │ │ ref_count: 3 │ │
│ │ Owner B │──┤ └──────────────────┘ │
│ └─────────┘ │ │
│ │ │
│ ┌─────────┐ │ │
│ │ Owner C │──┘ │
│ └─────────┘ │
│ │
└─────────────────────────────────────────────────────────────────┘Basic Usage
cpp
#include <memory>
// Create with make_shared (PREFERRED)
auto ptr = std::make_shared<int>(42);
// Copy creates another owner
auto ptr2 = ptr; // Now 2 owners
// Check reference count
std::cout << ptr.use_count() << "\n"; // 2
// Both ptr and ptr2 point to same value
std::cout << *ptr << " " << *ptr2 << "\n"; // 42 42Reference Counting
cpp
#include <memory>
void demonstrate() {
auto p1 = std::make_shared<int>(42);
std::cout << "Count: " << p1.use_count() << "\n"; // 1
{
auto p2 = p1; // Copy
std::cout << "Count: " << p1.use_count() << "\n"; // 2
auto p3 = p1; // Another copy
std::cout << "Count: " << p1.use_count() << "\n"; // 3
} // p2, p3 destroyed
std::cout << "Count: " << p1.use_count() << "\n"; // 1
} // p1 destroyed → ref_count = 0 → object deletedControl Block
┌─────────────────────────────────────────────────────────────────┐
│ CONTROL BLOCK STRUCTURE │
├─────────────────────────────────────────────────────────────────┤
│ │
│ shared_ptr Control Block │
│ ┌──────────────┐ ┌─────────────────────┐ │
│ │ object_ptr ──┼──────────► │ shared_count: 2 │ │
│ │ control_ptr ─┼──────┐ │ weak_count: 1 │ │
│ └──────────────┘ │ │ deleter │ │
│ └─────►│ allocator │ │
│ └─────────────────────┘ │
│ │ │
│ ▼ │
│ ┌─────────────────────┐ │
│ │ Managed Object │ │
│ └─────────────────────┘ │
│ │
│ Overhead: ~16-24 bytes for control block │
│ │
└─────────────────────────────────────────────────────────────────┘Creating shared_ptr
cpp
#include <memory>
// ✅ PREFERRED: make_shared (single allocation)
auto p1 = std::make_shared<Widget>(args...);
// ✅ OK: From unique_ptr
std::unique_ptr<Widget> up = std::make_unique<Widget>();
std::shared_ptr<Widget> sp = std::move(up);
// ⚠️ Less efficient: Constructor (two allocations)
std::shared_ptr<Widget> p2(new Widget());
// ❌ DANGER: Two shared_ptrs from same raw pointer
Widget* raw = new Widget();
std::shared_ptr<Widget> sp1(raw);
std::shared_ptr<Widget> sp2(raw); // 💥 Double delete!Sharing Across Containers
cpp
#include <memory>
#include <vector>
class Document {
public:
std::string content;
};
int main() {
auto doc = std::make_shared<Document>();
doc->content = "Hello";
std::vector<std::shared_ptr<Document>> editors;
std::vector<std::shared_ptr<Document>> viewers;
editors.push_back(doc);
viewers.push_back(doc);
viewers.push_back(doc);
std::cout << doc.use_count() << "\n"; // 4
// All share the same document
editors[0]->content = "Modified";
std::cout << viewers[1]->content << "\n"; // "Modified"
}Thread Safety
cpp
// shared_ptr itself is thread-safe for:
// ✅ Reading use_count()
// ✅ Copying shared_ptr instances
// ✅ Assigning shared_ptr instances
// But the OBJECT it points to is NOT automatically thread-safe!
auto ptr = std::make_shared<int>(0);
// Thread 1
auto copy1 = ptr; // ✅ Safe to copy
// Thread 2
auto copy2 = ptr; // ✅ Safe to copy
// Thread 1 & 2 both modifying *ptr
*copy1 = 10; // ❌ Race condition on the int!
*copy2 = 20; // ❌ Need mutex for object accessenable_shared_from_this
Khi object cần tạo shared_ptr của chính nó:
cpp
#include <memory>
class Node : public std::enable_shared_from_this<Node> {
public:
std::shared_ptr<Node> getShared() {
// ✅ Correct way
return shared_from_this();
// ❌ WRONG - creates new control block!
// return std::shared_ptr<Node>(this);
}
};
int main() {
auto node = std::make_shared<Node>();
auto same = node->getShared();
std::cout << node.use_count() << "\n"; // 2
}Custom Deleter
cpp
#include <memory>
void closeFile(FILE* f) {
if (f) fclose(f);
}
int main() {
// shared_ptr with custom deleter
std::shared_ptr<FILE> file(
fopen("data.txt", "r"),
closeFile
);
// Or with lambda
std::shared_ptr<FILE> file2(
fopen("data.txt", "r"),
[](FILE* f) { if (f) fclose(f); }
);
}unique_ptr vs shared_ptr
| Aspect | unique_ptr | shared_ptr |
|---|---|---|
| Owners | 1 | Many |
| Copy | ❌ No | ✅ Yes |
| Move | ✅ Yes | ✅ Yes |
| Overhead | None | Control block |
| Thread-safe | N/A | Ref counting |
| Default choice | ✅ Yes | When needed |
⚠️ Circular Reference Problem
cpp
struct Node {
std::shared_ptr<Node> next;
};
int main() {
auto a = std::make_shared<Node>();
auto b = std::make_shared<Node>();
a->next = b; // a → b
b->next = a; // b → a (circular!)
// When main exits:
// a.use_count() = 1 (from b->next)
// b.use_count() = 1 (from a->next)
// Neither reaches 0 → MEMORY LEAK!
}Solution: weak_ptr
📚 Tổng kết
| Feature | Description |
|---|---|
| Ownership | Shared (multiple owners) |
| Copy | ✅ Increases ref count |
| Thread safety | Ref counting is atomic |
| Overhead | Control block (~16-24 bytes) |
| When to use | When ownership must be shared |
➡️ Tiếp theo
Giải quyết vấn đề circular reference:
weak_ptr → — Breaking cycles.