Skip to content

🤝 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 42

Reference 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 deleted

Control 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 access

enable_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

Aspectunique_ptrshared_ptr
Owners1Many
Copy❌ No✅ Yes
Move✅ Yes✅ Yes
OverheadNoneControl block
Thread-safeN/ARef counting
Default choice✅ YesWhen 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

FeatureDescription
OwnershipShared (multiple owners)
Copy✅ Increases ref count
Thread safetyRef counting is atomic
OverheadControl block (~16-24 bytes)
When to useWhen ownership must be shared

➡️ Tiếp theo

Giải quyết vấn đề circular reference:

weak_ptr → — Breaking cycles.