S SmartDocs
系列: C++ cpp 187 行 · 更新于 2026-06-15

smart_pointers.cpp

C++/Part1_基礎入門/Ch06_指標與參考/smart_pointers.cpp

// ============================================================
// Ch06 — 智慧指標(Smart Pointers)
// 現代 C++ 的記憶體管理:RAII、unique_ptr、shared_ptr、weak_ptr
// 編譯:g++ -std=c++17 -Wall -o smart_pointers smart_pointers.cpp
// ============================================================

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

// ── 示範用的簡單類別:在建構/解構時印出訊息,方便觀察生命週期 ──
struct Resource {
    std::string name;

    explicit Resource(std::string n) : name(std::move(n)) {
        std::cout << "  [建構] Resource(" << name << ")\n";
    }
    ~Resource() {
        std::cout << "  [解構] ~Resource(" << name << ")\n";
    }
    void use() const {
        std::cout << "  使用中:" << name << "\n";
    }
};

// ── 函式原型 ──
void demonstrateRawPointerProblem();
void demonstrateUniquePtr();
void demonstrateUniquePtrOwnership();
void demonstrateSharedPtr();
void demonstrateWeakPtr();
void demonstrateSmartPtrAsParameter();

// 以「擁有權轉移」表達語意:函式接管資源
void takeOwnership(std::unique_ptr<Resource> res) {
    std::cout << "  takeOwnership 接管了 " << res->name << "\n";
    res->use();
    // 函式結束,res 離開作用域 → 自動 delete
}

// 以「只是使用、不擁有」表達語意:傳裸指標或參考
void observe(const Resource* res) {
    if (res != nullptr) {
        std::cout << "  observe(不擁有):" << res->name << "\n";
    }
}

// 共享擁有權:以值傳遞 shared_ptr(參考計數 +1)
void share(std::shared_ptr<Resource> res) {
    std::cout << "  share 內參考計數 = " << res.use_count() << "\n";
}

// ── 主程式 ────────────────────────────────────────
int main() {
    std::cout << "===== Ch06:智慧指標 =====\n\n";

    demonstrateRawPointerProblem();
    demonstrateUniquePtr();
    demonstrateUniquePtrOwnership();
    demonstrateSharedPtr();
    demonstrateWeakPtr();
    demonstrateSmartPtrAsParameter();

    return 0;
}

// ── 1. 裸指標的問題:手動管理容易出錯 ──
void demonstrateRawPointerProblem() {
    std::cout << "【1】裸指標的問題\n";

    Resource* raw = new Resource("RawPtr");
    raw->use();
    // 風險:
    //  - 忘記 delete  → 記憶體洩漏(memory leak)
    //  - 中途 return / 例外 → delete 不會執行
    //  - delete 兩次 → double free
    delete raw;        // 必須手動釋放
    raw = nullptr;     // 避免懸垂指標
    std::cout << "  手動 delete 完成(容易遺漏!)\n\n";
}

// ── 2. unique_ptr:獨佔擁有權,零額外開銷 ──
void demonstrateUniquePtr() {
    std::cout << "【2】unique_ptr(獨佔擁有權)\n";

    // 優先使用 make_unique(C++14),而非 new
    std::unique_ptr<Resource> p = std::make_unique<Resource>("Unique");
    p->use();
    std::cout << "  p.get() 位址 = " << p.get() << "\n";

    // unique_ptr 不可複製,但可以移動(轉移擁有權)
    std::unique_ptr<Resource> q = std::move(p);
    std::cout << "  std::move 後,p 是否為空? " << (p == nullptr ? "是" : "否") << "\n";
    q->use();

    // unique_ptr 也能管理陣列
    std::unique_ptr<int[]> arr = std::make_unique<int[]>(5);
    for (int i = 0; i < 5; i++) arr[i] = (i + 1) * 10;
    std::cout << "  unique_ptr<int[]>:";
    for (int i = 0; i < 5; i++) std::cout << arr[i] << " ";
    std::cout << "\n";

    std::cout << "  離開作用域時自動解構 →\n";
    // q 與 arr 在此自動釋放,不需 delete
}

// ── 3. unique_ptr 的擁有權轉移 ──
void demonstrateUniquePtrOwnership() {
    std::cout << "\n【3】unique_ptr 擁有權轉移\n";

    auto res = std::make_unique<Resource>("Movable");
    std::cout << "  將擁有權交給 takeOwnership:\n";
    takeOwnership(std::move(res));   // 明確轉移
    std::cout << "  回到 main,res 是否為空? " << (res == nullptr ? "是" : "否") << "\n\n";
}

// ── 4. shared_ptr:共享擁有權,參考計數 ──
void demonstrateSharedPtr() {
    std::cout << "【4】shared_ptr(共享擁有權)\n";

    std::shared_ptr<Resource> a = std::make_shared<Resource>("Shared");
    std::cout << "  建立 a,use_count = " << a.use_count() << "\n";

    {
        std::shared_ptr<Resource> b = a;   // 複製 → 計數 +1
        std::cout << "  複製為 b,use_count = " << a.use_count() << "\n";
        share(a);                          // 以值傳遞,函式內計數 +1
        std::cout << "  函式返回後,use_count = " << a.use_count() << "\n";
    }   // b 離開作用域 → 計數 -1

    std::cout << "  b 離開作用域後,use_count = " << a.use_count() << "\n";
    std::cout << "  當最後一個 shared_ptr 銷毀時才會解構 →\n\n";
}

// ── 5. weak_ptr:非擁有觀察者,打破循環參考 ──
struct Node {
    std::string name;
    std::shared_ptr<Node> next;     // 擁有下一個節點
    std::weak_ptr<Node>   prev;     // 觀察上一個節點(避免循環參考)

    explicit Node(std::string n) : name(std::move(n)) {
        std::cout << "  [建構] Node(" << name << ")\n";
    }
    ~Node() {
        std::cout << "  [解構] ~Node(" << name << ")\n";
    }
};

void demonstrateWeakPtr() {
    std::cout << "【5】weak_ptr(非擁有觀察者 / 打破循環)\n";

    auto first  = std::make_shared<Node>("First");
    auto second = std::make_shared<Node>("Second");

    first->next  = second;     // first 擁有 second
    second->prev = first;      // 若用 shared_ptr 會形成循環 → 記憶體洩漏!
                               // 改用 weak_ptr 即可正常釋放

    std::cout << "  first.use_count  = " << first.use_count() << "\n";
    std::cout << "  second.use_count = " << second.use_count() << "\n";

    // 透過 lock() 安全取得 weak_ptr 指向的物件
    if (auto p = second->prev.lock()) {
        std::cout << "  second 的前一個節點是:" << p->name << "\n";
    }

    std::cout << "  離開作用域,節點正確解構(無洩漏)→\n\n";
}

// ── 6. 以智慧指標表達函式的擁有權語意 ──
void demonstrateSmartPtrAsParameter() {
    std::cout << "【6】用參數型別表達擁有權語意\n";

    auto res = std::make_unique<Resource>("Param");

    // 只是「使用」→ 傳裸指標 / 參考,不轉移擁有權
    observe(res.get());

    std::cout << "  res 仍由 main 擁有,可繼續使用:\n";
    res->use();

    std::cout << "\n  口訣:\n";
    std::cout << "    接管擁有權 → 以值傳 unique_ptr\n";
    std::cout << "    共享擁有權 → 以值傳 shared_ptr\n";
    std::cout << "    只是使用   → 傳 T* 或 T&(非擁有)\n";
}

相关文章