S SmartDocs
Serie: C++ cpp 396 righe · Aggiornato 2026-04-03

shared_weak_ptr.cpp

C++/Part4_現代CPP/Ch16_記憶體管理與智慧指標/shared_weak_ptr.cpp

// ============================================================
// Ch16 — shared_ptr 與 weak_ptr 完整示範
// 編譯:g++ -std=c++17 -Wall -o shared_weak_ptr shared_weak_ptr.cpp
// ============================================================

#include <iostream>
#include <memory>
#include <string>
#include <vector>

// ── 輔助函式 ──
void section(const std::string& title) {
    std::cout << "\n========================================\n";
    std::cout << "  " << title << "\n";
    std::cout << "========================================\n";
}

// ── 示範用類別 ──
class Resource {
public:
    Resource(const std::string& name) : name_(name) {
        std::cout << "  Resource(\"" << name_ << "\") 建構\n";
    }
    ~Resource() {
        std::cout << "  Resource(\"" << name_ << "\") 解構\n";
    }
    void use() const {
        std::cout << "  使用 Resource(\"" << name_ << "\")\n";
    }
    const std::string& name() const { return name_; }

private:
    std::string name_;
};

// ── 循環參考問題示範 ──

// 有循環參考問題的版本
class BadNode {
public:
    std::string name;
    std::shared_ptr<BadNode> next;
    std::shared_ptr<BadNode> prev;  // 循環參考!

    BadNode(const std::string& n) : name(n) {
        std::cout << "  BadNode(\"" << name << "\") 建構\n";
    }
    ~BadNode() {
        std::cout << "  BadNode(\"" << name << "\") 解構\n";
    }
};

// 用 weak_ptr 修正的版本
class GoodNode {
public:
    std::string name;
    std::shared_ptr<GoodNode> next;
    std::weak_ptr<GoodNode> prev;   // 用 weak_ptr 打破循環

    GoodNode(const std::string& n) : name(n) {
        std::cout << "  GoodNode(\"" << name << "\") 建構\n";
    }
    ~GoodNode() {
        std::cout << "  GoodNode(\"" << name << "\") 解構\n";
    }
};

// ── 觀察者模式中的 weak_ptr ──
class Subject {
public:
    explicit Subject(const std::string& name) : name_(name) {}
    const std::string& name() const { return name_; }
private:
    std::string name_;
};

class Observer {
public:
    explicit Observer(const std::string& name) : name_(name) {}

    void watch(std::shared_ptr<Subject> subject) {
        subject_ = subject;  // 存為 weak_ptr,不影響生命週期
    }

    void report() const {
        if (auto sp = subject_.lock()) {
            std::cout << "  Observer(" << name_ << "): 觀察到 Subject(\""
                      << sp->name() << "\") 仍然存在\n";
        } else {
            std::cout << "  Observer(" << name_ << "): Subject 已被釋放\n";
        }
    }

private:
    std::string name_;
    std::weak_ptr<Subject> subject_;
};

int main() {
    std::cout << "===== Ch16:shared_ptr 與 weak_ptr 示範 =====\n";

    // ──────────────────────────────────────────
    // 1. 建立 shared_ptr
    // ──────────────────────────────────────────
    section("1. 建立 shared_ptr");
    {
        // make_shared(推薦)
        auto sp1 = std::make_shared<int>(42);
        std::cout << "make_shared<int>: " << *sp1 << "\n";
        std::cout << "use_count: " << sp1.use_count() << "\n";

        // 直接建構(不推薦)
        std::shared_ptr<double> sp2(new double(3.14));
        std::cout << "shared_ptr(new): " << *sp2 << "\n";

        // 自訂類別
        auto sp3 = std::make_shared<Resource>("Alpha");
        sp3->use();
        std::cout << "use_count: " << sp3.use_count() << "\n";
    }

    // ──────────────────────────────────────────
    // 2. 共享擁有權與參考計數
    // ──────────────────────────────────────────
    section("2. 共享擁有權與參考計數");
    {
        auto sp1 = std::make_shared<Resource>("Beta");
        std::cout << "建立後 use_count: " << sp1.use_count() << "\n";

        {
            auto sp2 = sp1;  // 複製 → 參考計數 +1
            std::cout << "複製後 use_count: " << sp1.use_count() << "\n";

            auto sp3 = sp1;  // 再複製 → 參考計數 +1
            std::cout << "再複製後 use_count: " << sp1.use_count() << "\n";

            sp2.reset();     // sp2 放棄 → 參考計數 -1
            std::cout << "sp2.reset() 後 use_count: " << sp1.use_count() << "\n";

        }  // sp3 離開作用域 → 參考計數 -1

        std::cout << "sp3 離開作用域後 use_count: " << sp1.use_count() << "\n";
    }  // sp1 離開 → 參考計數歸零 → Resource 解構
    std::cout << "(use_count 歸零,Resource 自動釋放)\n";

    // ──────────────────────────────────────────
    // 3. shared_ptr 的各種操作
    // ──────────────────────────────────────────
    section("3. shared_ptr 操作");
    {
        auto sp = std::make_shared<Resource>("Gamma");

        // 解參考
        std::cout << "sp->name(): " << sp->name() << "\n";

        // get() — 取得原始指標
        Resource* raw = sp.get();
        std::cout << "raw->name(): " << raw->name() << "\n";

        // bool 轉換
        if (sp) {
            std::cout << "sp 不為空\n";
        }

        // unique() — 是否只有一個擁有者(C++17 已棄用,C++20 移除)
        std::cout << "use_count == 1: " << (sp.use_count() == 1 ? "是" : "否") << "\n";

        // reset
        sp.reset();
        std::cout << "reset() 後 sp 為空: " << (!sp ? "是" : "否") << "\n";
    }

    // ──────────────────────────────────────────
    // 4. shared_ptr 與自訂刪除器
    // ──────────────────────────────────────────
    section("4. shared_ptr 自訂刪除器");
    {
        // 與 unique_ptr 不同,shared_ptr 的刪除器不是型別的一部分
        auto deleter = [](int* p) {
            std::cout << "  自訂刪除器:釋放 *p = " << *p << "\n";
            delete p;
        };

        std::shared_ptr<int> sp(new int(777), deleter);
        std::cout << "值:" << *sp << "\n";

        // 可以和其他 shared_ptr<int> 混用
        std::shared_ptr<int> sp2 = sp;
        std::cout << "use_count: " << sp.use_count() << "\n";

        sp.reset();
        std::cout << "sp.reset() 後 use_count (sp2): " << sp2.use_count() << "\n";
    }

    // ──────────────────────────────────────────
    // 5. shared_ptr 在容器中
    // ──────────────────────────────────────────
    section("5. shared_ptr 在容器中");
    {
        std::vector<std::shared_ptr<Resource>> resources;

        auto r1 = std::make_shared<Resource>("Res-A");
        auto r2 = std::make_shared<Resource>("Res-B");

        resources.push_back(r1);
        resources.push_back(r2);
        resources.push_back(r1);  // r1 被多次共享

        std::cout << "\n容器內容:\n";
        for (const auto& r : resources) {
            std::cout << "  " << r->name()
                      << " (use_count: " << r.use_count() << ")\n";
        }

        std::cout << "\nr1 的 use_count: " << r1.use_count() << "\n";
        std::cout << "r2 的 use_count: " << r2.use_count() << "\n";
    }

    // ──────────────────────────────────────────
    // 6. weak_ptr 基礎
    // ──────────────────────────────────────────
    section("6. weak_ptr 基礎");
    {
        std::shared_ptr<int> sp = std::make_shared<int>(42);
        std::weak_ptr<int> wp = sp;

        std::cout << "sp use_count: " << sp.use_count() << "\n";
        std::cout << "wp.expired(): " << (wp.expired() ? "是" : "否") << "\n";

        // lock() — 取得 shared_ptr
        if (auto locked = wp.lock()) {
            std::cout << "lock() 成功:*locked = " << *locked << "\n";
            std::cout << "lock 後 use_count: " << sp.use_count() << "\n";
        }
        // locked 離開作用域,use_count 恢復
        std::cout << "locked 銷毀後 use_count: " << sp.use_count() << "\n";

        // 釋放 shared_ptr 後
        sp.reset();
        std::cout << "\nsp.reset() 後:\n";
        std::cout << "wp.expired(): " << (wp.expired() ? "是" : "否") << "\n";

        if (auto locked = wp.lock()) {
            std::cout << "lock() 成功\n";  // 不會執行
        } else {
            std::cout << "lock() 失敗 — 物件已釋放\n";
        }
    }

    // ──────────────────────────────────────────
    // 7. 循環參考問題(shared_ptr 互相持有)
    // ──────────────────────────────────────────
    section("7. 循環參考問題(BadNode)");
    {
        std::cout << "建立兩個 BadNode 並互相連結:\n";
        auto node1 = std::make_shared<BadNode>("A");
        auto node2 = std::make_shared<BadNode>("B");

        node1->next = node2;  // A → B
        node2->prev = node1;  // B → A(循環!)

        std::cout << "node1 use_count: " << node1.use_count() << "\n";
        std::cout << "node2 use_count: " << node2.use_count() << "\n";
    }
    std::cout << "→ 離開作用域後 BadNode 的解構子「沒有」被呼叫 → 記憶體洩漏!\n";
    // node1 的 use_count 從 2 降到 1(node2->prev 仍持有)
    // node2 的 use_count 從 2 降到 1(node1->next 仍持有)
    // 兩者都無法歸零 → 永遠不會釋放

    // ──────────────────────────────────────────
    // 8. 用 weak_ptr 打破循環參考(GoodNode)
    // ──────────────────────────────────────────
    section("8. weak_ptr 打破循環(GoodNode)");
    {
        std::cout << "建立兩個 GoodNode 並連結(prev 用 weak_ptr):\n";
        auto node1 = std::make_shared<GoodNode>("X");
        auto node2 = std::make_shared<GoodNode>("Y");

        node1->next = node2;  // X → Y(shared_ptr)
        node2->prev = node1;  // Y → X(weak_ptr,不增加計數)

        std::cout << "node1 use_count: " << node1.use_count() << "\n";
        std::cout << "node2 use_count: " << node2.use_count() << "\n";

        // 透過 weak_ptr 存取
        if (auto prev = node2->prev.lock()) {
            std::cout << "node2 的前一個節點:" << prev->name << "\n";
        }
    }
    std::cout << "→ GoodNode 的解構子正確被呼叫 ✓\n";

    // ──────────────────────────────────────────
    // 9. 三節點鏈結串列示範
    // ──────────────────────────────────────────
    section("9. 三節點鏈結串列");
    {
        auto n1 = std::make_shared<GoodNode>("Head");
        auto n2 = std::make_shared<GoodNode>("Middle");
        auto n3 = std::make_shared<GoodNode>("Tail");

        // 建立雙向連結
        n1->next = n2;
        n2->prev = n1;
        n2->next = n3;
        n3->prev = n2;

        // 正向走訪
        std::cout << "正向走訪:";
        for (auto current = n1; current; current = current->next) {
            std::cout << current->name;
            if (current->next) std::cout << " → ";
        }
        std::cout << "\n";

        // 反向走訪(從 n3 開始)
        std::cout << "反向走訪:";
        auto current = n3;
        while (current) {
            std::cout << current->name;
            if (auto prev = current->prev.lock()) {
                std::cout << " → ";
                current = prev;
            } else {
                break;
            }
        }
        std::cout << "\n";

        std::cout << "n1 use_count: " << n1.use_count()
                  << ", n2 use_count: " << n2.use_count()
                  << ", n3 use_count: " << n3.use_count() << "\n";
    }

    // ──────────────────────────────────────────
    // 10. 觀察者模式中的 weak_ptr
    // ──────────────────────────────────────────
    section("10. 觀察者模式與 weak_ptr");
    {
        Observer obs1("Watcher-1");
        Observer obs2("Watcher-2");

        {
            auto subject = std::make_shared<Subject>("重要事件");
            obs1.watch(subject);
            obs2.watch(subject);

            std::cout << "Subject 存在時:\n";
            obs1.report();
            obs2.report();
        }

        std::cout << "\nSubject 已釋放後:\n";
        obs1.report();
        obs2.report();
    }

    // ──────────────────────────────────────────
    // 11. make_shared vs new 的差異
    // ──────────────────────────────────────────
    section("11. make_shared vs new");
    {
        std::cout << "make_shared 的優點:\n";
        std::cout << "  1. 只做一次記憶體配置(物件 + 控制區塊合併)\n";
        std::cout << "  2. 例外安全(不會有部分建構的問題)\n";
        std::cout << "  3. 更好的快取局部性\n";

        std::cout << "\nmake_shared 的限制:\n";
        std::cout << "  1. 無法使用自訂刪除器\n";
        std::cout << "  2. 即使所有 shared_ptr 都釋放了,\n";
        std::cout << "     若仍有 weak_ptr 存在,記憶體不會完全釋放\n";
        std::cout << "     (因為控制區塊和物件在同一塊記憶體中)\n";

        std::cout << "\n概念圖:\n";
        std::cout << "  make_shared:     [控制區塊 + 物件] ← 一次配置\n";
        std::cout << "  shared_ptr(new): [控制區塊] [物件] ← 兩次配置\n";
    }

    // ──────────────────────────────────────────
    // 12. 智慧指標選擇總結
    // ──────────────────────────────────────────
    section("12. 智慧指標選擇總結");
    {
        std::cout << "┌──────────────┬──────────────────────────────┐\n";
        std::cout << "│ 場景          │ 建議                          │\n";
        std::cout << "├──────────────┼──────────────────────────────┤\n";
        std::cout << "│ 獨佔擁有      │ unique_ptr(預設選擇)         │\n";
        std::cout << "│ 共享擁有      │ shared_ptr                    │\n";
        std::cout << "│ 觀察不擁有    │ weak_ptr + shared_ptr         │\n";
        std::cout << "│ 效能優先      │ unique_ptr(零額外開銷)       │\n";
        std::cout << "│ 打破循環      │ weak_ptr                      │\n";
        std::cout << "└──────────────┴──────────────────────────────┘\n";
    }

    std::cout << "\n===== shared_ptr / weak_ptr 示範結束 =====\n";
    return 0;
}

Articoli correlati