<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Smart Pointers on kenji.blog</title><link>http://kenji.blog/zh-tw/tags/smart-pointers/</link><description>Recent content in Smart Pointers on kenji.blog</description><generator>Hugo -- gohugo.io</generator><language>zh-tw</language><copyright>kenjinote</copyright><lastBuildDate>Sat, 12 Sep 2026 07:00:00 +0900</lastBuildDate><atom:link href="http://kenji.blog/zh-tw/tags/smart-pointers/index.xml" rel="self" type="application/rss+xml"/><item><title>防止記憶體外洩的智慧指標（std::unique_ptr / shared_ptr）活用術</title><link>http://kenji.blog/zh-tw/p/cpp-smart-pointers-guide-unique-shared-ptr/</link><pubDate>Sat, 12 Sep 2026 07:00:00 +0900</pubDate><guid>http://kenji.blog/zh-tw/p/cpp-smart-pointers-guide-unique-shared-ptr/</guid><description>&lt;img src="http://kenji.blog/p/cpp-smart-pointers-guide-unique-shared-ptr/img/eyecatch.jpg" alt="Featured image of post 防止記憶體外洩的智慧指標（std::unique_ptr / shared_ptr）活用術" />&lt;p>在 C++ 中的記憶體管理，長年以來都是開發者面臨的最大挑戰之一。依賴手動使用 &lt;code>new&lt;/code> 與 &lt;code>delete&lt;/code> 的傳統記憶體管理風格，成為了引發記憶體外洩 (Memory Leak)、懸空指標 (Dangling Pointer)、雙重釋放 (Double Free) 等嚴重 Bug 的溫床。然而，隨著 Modern C++ (C++11 起) 的問世，情況發生了戲劇性的變化。其核心正是「智慧指標 (Smart Pointers)」。&lt;/p>
&lt;p>本文將針對 &lt;code>std::unique_ptr&lt;/code>、&lt;code>std::shared_ptr&lt;/code> 以及 &lt;code>std::weak_ptr&lt;/code> 這些能夠根除記憶體外洩並實現安全且高效率資源管理的強大工具，介紹其運作機制與進階活用術，並結合內部實作（控制區塊與原子操作）、對效能的影響、以及透過數學模型進行參照計數的公式化，進行極為詳細的解說。&lt;/p>
&lt;h2 id="1-簡介c-記憶體管理的黑暗時代與-modern-c-的黎明">1. 簡介：C++ 記憶體管理的黑暗時代與 Modern C++ 的黎明
&lt;/h2>&lt;p>在過去的 C++ 開發中，開發者必須親自負責釋放配置在堆積 (Heap) 上的記憶體。&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;span class="lnt">3
&lt;/span>&lt;span class="lnt">4
&lt;/span>&lt;span class="lnt">5
&lt;/span>&lt;span class="lnt">6
&lt;/span>&lt;span class="lnt">7
&lt;/span>&lt;span class="lnt">8
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-cpp" data-lang="cpp">&lt;span class="line">&lt;span class="cl">&lt;span class="kt">void&lt;/span> &lt;span class="nf">legacy_function&lt;/span>&lt;span class="p">()&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kt">int&lt;/span>&lt;span class="o">*&lt;/span> &lt;span class="n">ptr&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="k">new&lt;/span> &lt;span class="kt">int&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="mi">10&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1">// ... 某些處理 ...
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="k">if&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="n">some_condition&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">return&lt;/span>&lt;span class="p">;&lt;/span> &lt;span class="c1">// 發生記憶體外洩！delete 未被呼叫
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">delete&lt;/span> &lt;span class="n">ptr&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>在上述的程式碼中，如果發生例外或是進行了提早回傳 (Early Return)，&lt;code>delete&lt;/code> 將會被跳過，進而引發記憶體外洩。為了防止這種情況，便出現了「RAII (Resource Acquisition Is Initialization)」這個典範。RAII 是一種將資源的獲取綁定於物件的初始化 (建構子)，並將資源的釋放綁定於物件的銷毀 (解構子) 的手法。智慧指標就是將這種 RAII 慣用語應用於記憶體管理的標準函式庫類別。&lt;/p>
&lt;h2 id="2-stdunique_ptr零開銷的獨佔所有權">2. &lt;code>std::unique_ptr&lt;/code>：零開銷的獨佔所有權
&lt;/h2>&lt;p>&lt;code>std::unique_ptr&lt;/code> 是一種對於動態配置物件擁有「獨佔所有權 (Exclusive Ownership)」的智慧指標。能夠擁有某項資源的 &lt;code>unique_ptr&lt;/code> 永遠只有一個。&lt;/p>
&lt;h3 id="21-零開銷原則">2.1 零開銷原則
&lt;/h3>&lt;p>&lt;code>std::unique_ptr&lt;/code> 最大的魅力在於其效能。在未自訂刪除器 (Custom Deleter) 的預設狀態下，&lt;code>std::unique_ptr&lt;/code> 的大小與原生指標 (Raw Pointer) 完全相同。它不具有任何不必要的成員變數，也沒有使用虛擬函式。透過編譯器最佳化，經由 &lt;code>std::unique_ptr&lt;/code> 進行的存取將被展開成與原生指標同等的組合語言程式碼。&lt;/p>
&lt;h3 id="22-所有權的轉移與-stdmove">2.2 所有權的轉移與 &lt;code>std::move&lt;/code>
&lt;/h3>&lt;p>由於擁有獨佔所有權，&lt;code>std::unique_ptr&lt;/code> 無法被複製 (其複製建構子與複製指派運算子已被標記為 &lt;code>delete&lt;/code>)。若要將所有權轉移給另一個 &lt;code>unique_ptr&lt;/code>，必須使用 &lt;code>std::move&lt;/code> 來利用移動語意 (Move Semantics)。&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt"> 1
&lt;/span>&lt;span class="lnt"> 2
&lt;/span>&lt;span class="lnt"> 3
&lt;/span>&lt;span class="lnt"> 4
&lt;/span>&lt;span class="lnt"> 5
&lt;/span>&lt;span class="lnt"> 6
&lt;/span>&lt;span class="lnt"> 7
&lt;/span>&lt;span class="lnt"> 8
&lt;/span>&lt;span class="lnt"> 9
&lt;/span>&lt;span class="lnt">10
&lt;/span>&lt;span class="lnt">11
&lt;/span>&lt;span class="lnt">12
&lt;/span>&lt;span class="lnt">13
&lt;/span>&lt;span class="lnt">14
&lt;/span>&lt;span class="lnt">15
&lt;/span>&lt;span class="lnt">16
&lt;/span>&lt;span class="lnt">17
&lt;/span>&lt;span class="lnt">18
&lt;/span>&lt;span class="lnt">19
&lt;/span>&lt;span class="lnt">20
&lt;/span>&lt;span class="lnt">21
&lt;/span>&lt;span class="lnt">22
&lt;/span>&lt;span class="lnt">23
&lt;/span>&lt;span class="lnt">24
&lt;/span>&lt;span class="lnt">25
&lt;/span>&lt;span class="lnt">26
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-cpp" data-lang="cpp">&lt;span class="line">&lt;span class="cl">&lt;span class="cp">#include&lt;/span> &lt;span class="cpf">&amp;lt;iostream&amp;gt;&lt;/span>&lt;span class="cp">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="cp">#include&lt;/span> &lt;span class="cpf">&amp;lt;memory&amp;gt;&lt;/span>&lt;span class="cp">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="cp">&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">class&lt;/span> &lt;span class="nc">Resource&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">public&lt;/span>&lt;span class="o">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">Resource&lt;/span>&lt;span class="p">()&lt;/span> &lt;span class="p">{&lt;/span> &lt;span class="n">std&lt;/span>&lt;span class="o">::&lt;/span>&lt;span class="n">cout&lt;/span> &lt;span class="o">&amp;lt;&amp;lt;&lt;/span> &lt;span class="s">&amp;#34;Resource acquired&lt;/span>&lt;span class="se">\n&lt;/span>&lt;span class="s">&amp;#34;&lt;/span>&lt;span class="p">;&lt;/span> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="o">~&lt;/span>&lt;span class="n">Resource&lt;/span>&lt;span class="p">()&lt;/span> &lt;span class="p">{&lt;/span> &lt;span class="n">std&lt;/span>&lt;span class="o">::&lt;/span>&lt;span class="n">cout&lt;/span> &lt;span class="o">&amp;lt;&amp;lt;&lt;/span> &lt;span class="s">&amp;#34;Resource destroyed&lt;/span>&lt;span class="se">\n&lt;/span>&lt;span class="s">&amp;#34;&lt;/span>&lt;span class="p">;&lt;/span> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kt">void&lt;/span> &lt;span class="nf">do_something&lt;/span>&lt;span class="p">()&lt;/span> &lt;span class="p">{&lt;/span> &lt;span class="n">std&lt;/span>&lt;span class="o">::&lt;/span>&lt;span class="n">cout&lt;/span> &lt;span class="o">&amp;lt;&amp;lt;&lt;/span> &lt;span class="s">&amp;#34;Doing something&lt;/span>&lt;span class="se">\n&lt;/span>&lt;span class="s">&amp;#34;&lt;/span>&lt;span class="p">;&lt;/span> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">};&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kt">void&lt;/span> &lt;span class="nf">process_resource&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">std&lt;/span>&lt;span class="o">::&lt;/span>&lt;span class="n">unique_ptr&lt;/span>&lt;span class="o">&amp;lt;&lt;/span>&lt;span class="n">Resource&lt;/span>&lt;span class="o">&amp;gt;&lt;/span> &lt;span class="n">ptr&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">ptr&lt;/span>&lt;span class="o">-&amp;gt;&lt;/span>&lt;span class="n">do_something&lt;/span>&lt;span class="p">();&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1">// 離開作用域時 ptr 會被銷毀，Resource 也會被釋放
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span>&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kt">int&lt;/span> &lt;span class="nf">main&lt;/span>&lt;span class="p">()&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">std&lt;/span>&lt;span class="o">::&lt;/span>&lt;span class="n">unique_ptr&lt;/span>&lt;span class="o">&amp;lt;&lt;/span>&lt;span class="n">Resource&lt;/span>&lt;span class="o">&amp;gt;&lt;/span> &lt;span class="n">my_ptr&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">std&lt;/span>&lt;span class="o">::&lt;/span>&lt;span class="n">make_unique&lt;/span>&lt;span class="o">&amp;lt;&lt;/span>&lt;span class="n">Resource&lt;/span>&lt;span class="o">&amp;gt;&lt;/span>&lt;span class="p">();&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1">// process_resource(my_ptr); // 錯誤：無法複製
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="n">process_resource&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">std&lt;/span>&lt;span class="o">::&lt;/span>&lt;span class="n">move&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">my_ptr&lt;/span>&lt;span class="p">));&lt;/span> &lt;span class="c1">// 轉移所有權
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="o">!&lt;/span>&lt;span class="n">my_ptr&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">std&lt;/span>&lt;span class="o">::&lt;/span>&lt;span class="n">cout&lt;/span> &lt;span class="o">&amp;lt;&amp;lt;&lt;/span> &lt;span class="s">&amp;#34;my_ptr is now empty.&lt;/span>&lt;span class="se">\n&lt;/span>&lt;span class="s">&amp;#34;&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="mi">0&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>以下的 Mermaid 圖表展示了透過 &lt;code>std::move&lt;/code> 轉移所有權的概念。&lt;/p>
&lt;div class="mermaid">graph LR
subgraph "std::move 之前"
A["unique_ptr (ptr1)"] -->|"擁有"| B["堆積記憶體 (物件)"]
end
subgraph "std::move 之後"
C["unique_ptr (ptr1)"] -.->|"空 (nullptr)"| D["nullptr"]
E["unique_ptr (ptr2)"] -->|"擁有"| F["堆積記憶體 (物件)"]
end&lt;/div>
&lt;h3 id="23-自訂刪除器的實作">2.3 自訂刪除器的實作
&lt;/h3>&lt;p>當包裝 C 語言的舊式 API (例如 &lt;code>FILE*&lt;/code> 或 Socket 等) 時，釋放記憶體時需要呼叫 &lt;code>delete&lt;/code> 以外的函式 (如 &lt;code>fclose&lt;/code> 等)。&lt;code>std::unique_ptr&lt;/code> 可以在第二個樣板引數 (Template Argument) 中指定自訂刪除器 (Custom Deleter)。&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt"> 1
&lt;/span>&lt;span class="lnt"> 2
&lt;/span>&lt;span class="lnt"> 3
&lt;/span>&lt;span class="lnt"> 4
&lt;/span>&lt;span class="lnt"> 5
&lt;/span>&lt;span class="lnt"> 6
&lt;/span>&lt;span class="lnt"> 7
&lt;/span>&lt;span class="lnt"> 8
&lt;/span>&lt;span class="lnt"> 9
&lt;/span>&lt;span class="lnt">10
&lt;/span>&lt;span class="lnt">11
&lt;/span>&lt;span class="lnt">12
&lt;/span>&lt;span class="lnt">13
&lt;/span>&lt;span class="lnt">14
&lt;/span>&lt;span class="lnt">15
&lt;/span>&lt;span class="lnt">16
&lt;/span>&lt;span class="lnt">17
&lt;/span>&lt;span class="lnt">18
&lt;/span>&lt;span class="lnt">19
&lt;/span>&lt;span class="lnt">20
&lt;/span>&lt;span class="lnt">21
&lt;/span>&lt;span class="lnt">22
&lt;/span>&lt;span class="lnt">23
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-cpp" data-lang="cpp">&lt;span class="line">&lt;span class="cl">&lt;span class="cp">#include&lt;/span> &lt;span class="cpf">&amp;lt;cstdio&amp;gt;&lt;/span>&lt;span class="cp">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="cp">#include&lt;/span> &lt;span class="cpf">&amp;lt;memory&amp;gt;&lt;/span>&lt;span class="cp">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="cp">&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">// 用於自訂刪除器的仿函式 (Functor)
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span>&lt;span class="k">struct&lt;/span> &lt;span class="nc">FileDeleter&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kt">void&lt;/span> &lt;span class="nf">operator&lt;/span>&lt;span class="p">()(&lt;/span>&lt;span class="n">FILE&lt;/span>&lt;span class="o">*&lt;/span> &lt;span class="n">fp&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="k">const&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="n">fp&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">std&lt;/span>&lt;span class="o">::&lt;/span>&lt;span class="n">cout&lt;/span> &lt;span class="o">&amp;lt;&amp;lt;&lt;/span> &lt;span class="s">&amp;#34;Closing file.&lt;/span>&lt;span class="se">\n&lt;/span>&lt;span class="s">&amp;#34;&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">std&lt;/span>&lt;span class="o">::&lt;/span>&lt;span class="n">fclose&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">fp&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">};&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">using&lt;/span> &lt;span class="n">UniqueFile&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">std&lt;/span>&lt;span class="o">::&lt;/span>&lt;span class="n">unique_ptr&lt;/span>&lt;span class="o">&amp;lt;&lt;/span>&lt;span class="n">FILE&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">FileDeleter&lt;/span>&lt;span class="o">&amp;gt;&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kt">int&lt;/span> &lt;span class="nf">main&lt;/span>&lt;span class="p">()&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">UniqueFile&lt;/span> &lt;span class="n">file&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">std&lt;/span>&lt;span class="o">::&lt;/span>&lt;span class="n">fopen&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#34;test.txt&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s">&amp;#34;w&amp;#34;&lt;/span>&lt;span class="p">));&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="n">file&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">std&lt;/span>&lt;span class="o">::&lt;/span>&lt;span class="n">fputs&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#34;Hello, Smart Pointers!&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">file&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">get&lt;/span>&lt;span class="p">());&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1">// 離開作用域時會呼叫 FileDeleter 進行 fclose
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="k">return&lt;/span> &lt;span class="mi">0&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>若將函式指標或 Lambda 運算式作為自訂刪除器使用，可能會增加 &lt;code>unique_ptr&lt;/code> 的大小；但如果像上述一樣使用無狀態的函式物件 (Functor)，受惠於 C++ 的 &lt;strong>EBCO (Empty Base Class Optimization)&lt;/strong> 或 C++20 的 &lt;code>[[no_unique_address]]&lt;/code>，其大小不會比原生指標增加 (保持零開銷)。&lt;/p>
&lt;h2 id="3-stdshared_ptr共享所有權與控制區塊">3. &lt;code>std::shared_ptr&lt;/code>：共享所有權與控制區塊
&lt;/h2>&lt;p>&lt;code>std::shared_ptr&lt;/code> 是一種讓多個指標共享並擁有同一個物件的智慧指標。當最後一個 &lt;code>shared_ptr&lt;/code> 被銷毀時，其管理的物件就會被釋放。&lt;/p>
&lt;h3 id="31-內部架構控制區塊">3.1 內部架構：控制區塊
&lt;/h3>&lt;p>&lt;code>std::shared_ptr&lt;/code> 除了指向被管理物件的指標外，還會在堆積上配置並共享一種被稱為 &lt;strong>控制區塊 (Control Block)&lt;/strong> 的詮釋資料 (Metadata)。控制區塊包含以下資訊：&lt;/p>
&lt;ol>
&lt;li>&lt;strong>強參照計數 (Strong Count)&lt;/strong>：擁有該物件的 &lt;code>shared_ptr&lt;/code> 數量。當此數量變為 0 時，物件就會被銷毀。&lt;/li>
&lt;li>&lt;strong>弱參照計數 (Weak Count)&lt;/strong>：正在監視該物件的 &lt;code>weak_ptr&lt;/code> 數量。當強參照計數與弱參照計數雙雙變為 0 時，控制區塊本身就會被釋放。&lt;/li>
&lt;li>&lt;strong>自訂刪除器與配置器&lt;/strong> (如果有被指定)。&lt;/li>
&lt;/ol>
&lt;div class="mermaid">graph TD
A["std::shared_ptr&lt;T> (sp1)"] -->|"指向 T 的指標"| B["被管理的物件 (T)"]
A -->|"指向控制區塊的指標"| C["控制區塊"]
D["std::shared_ptr&lt;T> (sp2)"] -->|"指向 T 的指標"| B
D -->|"指向控制區塊的指標"| C
C -->|"刪除"| B
C -.->|"強參照計數：2"| E["強參照計數"]
C -.->|"弱參照計數：0"| F["弱參照計數"]
C -.->|"自訂刪除器"| G["刪除器"]&lt;/div>
&lt;p>因此，&lt;code>std::shared_ptr&lt;/code> 物件本身的大小通常會是原生指標的 2 倍 (指向物件的指標，以及指向控制區塊的指標)。&lt;/p>
&lt;h3 id="32-效能與原子操作">3.2 效能與原子操作
&lt;/h3>&lt;p>控制區塊內的參照計數被實作為 &lt;strong>原子操作 (Atomic Operations)&lt;/strong>，以確保在多執行緒環境下也能安全地增減。&lt;/p>
&lt;p>在 x86/x64 架構中，參照計數的增減會使用如 &lt;code>lock xadd&lt;/code> 之類的原子指令。與一般的整數加法相比，這會伴隨數十個週期 (Cycles) 的開銷。因此，如果以傳值 (By Value) 的方式將 &lt;code>shared_ptr&lt;/code> 傳遞給函式，每次複製都會發生原子的遞增與遞減，進而降低效能。&lt;/p>
&lt;p>&lt;strong>最佳實務 (Best Practice)&lt;/strong>：將 &lt;code>shared_ptr&lt;/code> 傳遞給函式時，除非需要共享所有權，否則應作為 &lt;code>const std::shared_ptr&amp;lt;T&amp;gt;&amp;amp;&lt;/code> (常數參照) 來傳遞，或是傳遞原生指標/參照。&lt;/p>
&lt;h3 id="33-stdmake_shared-vs-new">3.3 &lt;code>std::make_shared&lt;/code> vs &lt;code>new&lt;/code>
&lt;/h3>&lt;p>在建立 &lt;code>shared_ptr&lt;/code> 時，應盡可能使用 &lt;code>std::make_shared&lt;/code>。這有兩個重大的原因。&lt;/p>
&lt;ol>
&lt;li>&lt;strong>記憶體配置最佳化&lt;/strong>：
若使用 &lt;code>new&lt;/code>，會發生兩次堆積配置：一次用於物件本身，另一次用於控制區塊。若使用 &lt;code>std::make_shared&lt;/code>，則能以一次堆積配置來保留包含兩者在內的一個大型記憶體區塊，並且也能提升快取效率。&lt;/li>
&lt;li>&lt;strong>例外安全性&lt;/strong>：
在早於 C++17 的標準中，函式引數的評估順序是未規定的；如果在將 &lt;code>new&lt;/code> 配置的指標傳給 &lt;code>shared_ptr&lt;/code> 的建構子之前，其他引數的評估過程中發生了例外，就會有記憶體外洩的風險。&lt;code>make_shared&lt;/code> 能夠完全避免這個問題。&lt;/li>
&lt;/ol>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;span class="lnt">3
&lt;/span>&lt;span class="lnt">4
&lt;/span>&lt;span class="lnt">5
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-cpp" data-lang="cpp">&lt;span class="line">&lt;span class="cl">&lt;span class="c1">// 應避免的寫法 (2 次記憶體配置)
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span>&lt;span class="n">std&lt;/span>&lt;span class="o">::&lt;/span>&lt;span class="n">shared_ptr&lt;/span>&lt;span class="o">&amp;lt;&lt;/span>&lt;span class="n">MyClass&lt;/span>&lt;span class="o">&amp;gt;&lt;/span> &lt;span class="n">ptr1&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="k">new&lt;/span> &lt;span class="n">MyClass&lt;/span>&lt;span class="p">());&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">// 推薦的寫法 (1 次記憶體配置)
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span>&lt;span class="n">std&lt;/span>&lt;span class="o">::&lt;/span>&lt;span class="n">shared_ptr&lt;/span>&lt;span class="o">&amp;lt;&lt;/span>&lt;span class="n">MyClass&lt;/span>&lt;span class="o">&amp;gt;&lt;/span> &lt;span class="n">ptr2&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">std&lt;/span>&lt;span class="o">::&lt;/span>&lt;span class="n">make_shared&lt;/span>&lt;span class="o">&amp;lt;&lt;/span>&lt;span class="n">MyClass&lt;/span>&lt;span class="o">&amp;gt;&lt;/span>&lt;span class="p">();&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;h2 id="4-stdweak_ptr循環參照的解決與監視">4. &lt;code>std::weak_ptr&lt;/code>：循環參照的解決與監視
&lt;/h2>&lt;p>共享所有權有著一個被稱為「循環參照 (Circular References)」的致命弱點。當物件 A 與物件 B 互相以 &lt;code>shared_ptr&lt;/code> 指向對方時，各自的強參照計數最少都會維持在 1，直到程式結束為止都絕對不會變成 0，進而導致記憶體外洩。&lt;/p>
&lt;div class="mermaid">graph TD
subgraph "循環參照 (記憶體外洩)"
A["物件 A"] -->|"shared_ptr (強參照=1)"| B["物件 B"]
B -->|"shared_ptr (強參照=1)"| A
end&lt;/div>
&lt;h3 id="41-透過-stdweak_ptr-打破循環">4.1 透過 &lt;code>std::weak_ptr&lt;/code> 打破循環
&lt;/h3>&lt;p>解決這個問題的就是 &lt;code>std::weak_ptr&lt;/code>。&lt;code>weak_ptr&lt;/code> 是由 &lt;code>shared_ptr&lt;/code> 所建立並參照著該物件，但&lt;strong>不會增加強參照計數&lt;/strong>。取而代之的是增加弱參照計數。藉由這種方式，可以在不擁有所有權的情況下「監視」物件。&lt;/p>
&lt;div class="mermaid">graph TD
subgraph "打破循環參照"
C["物件 A"] -->|"shared_ptr (強參照=1)"| D["物件 B"]
D -.->|"weak_ptr (弱參照=1)"| C
end&lt;/div>
&lt;h3 id="42-透過-lock-方法進行安全存取">4.2 透過 &lt;code>lock()&lt;/code> 方法進行安全存取
&lt;/h3>&lt;p>&lt;code>weak_ptr&lt;/code> 並不具備直接存取物件的運算子 (&lt;code>-&amp;gt;&lt;/code> 或 &lt;code>*&lt;/code>)。這是因為目標物件有可能已經被銷毀了。為了安全地進行存取，必須呼叫 &lt;code>lock()&lt;/code> 方法來暫時取得一個 &lt;code>shared_ptr&lt;/code>。&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt"> 1
&lt;/span>&lt;span class="lnt"> 2
&lt;/span>&lt;span class="lnt"> 3
&lt;/span>&lt;span class="lnt"> 4
&lt;/span>&lt;span class="lnt"> 5
&lt;/span>&lt;span class="lnt"> 6
&lt;/span>&lt;span class="lnt"> 7
&lt;/span>&lt;span class="lnt"> 8
&lt;/span>&lt;span class="lnt"> 9
&lt;/span>&lt;span class="lnt">10
&lt;/span>&lt;span class="lnt">11
&lt;/span>&lt;span class="lnt">12
&lt;/span>&lt;span class="lnt">13
&lt;/span>&lt;span class="lnt">14
&lt;/span>&lt;span class="lnt">15
&lt;/span>&lt;span class="lnt">16
&lt;/span>&lt;span class="lnt">17
&lt;/span>&lt;span class="lnt">18
&lt;/span>&lt;span class="lnt">19
&lt;/span>&lt;span class="lnt">20
&lt;/span>&lt;span class="lnt">21
&lt;/span>&lt;span class="lnt">22
&lt;/span>&lt;span class="lnt">23
&lt;/span>&lt;span class="lnt">24
&lt;/span>&lt;span class="lnt">25
&lt;/span>&lt;span class="lnt">26
&lt;/span>&lt;span class="lnt">27
&lt;/span>&lt;span class="lnt">28
&lt;/span>&lt;span class="lnt">29
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-cpp" data-lang="cpp">&lt;span class="line">&lt;span class="cl">&lt;span class="cp">#include&lt;/span> &lt;span class="cpf">&amp;lt;iostream&amp;gt;&lt;/span>&lt;span class="cp">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="cp">#include&lt;/span> &lt;span class="cpf">&amp;lt;memory&amp;gt;&lt;/span>&lt;span class="cp">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="cp">&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">class&lt;/span> &lt;span class="nc">Node&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">public&lt;/span>&lt;span class="o">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">std&lt;/span>&lt;span class="o">::&lt;/span>&lt;span class="n">string&lt;/span> &lt;span class="n">name&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">std&lt;/span>&lt;span class="o">::&lt;/span>&lt;span class="n">shared_ptr&lt;/span>&lt;span class="o">&amp;lt;&lt;/span>&lt;span class="n">Node&lt;/span>&lt;span class="o">&amp;gt;&lt;/span> &lt;span class="n">next&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">std&lt;/span>&lt;span class="o">::&lt;/span>&lt;span class="n">weak_ptr&lt;/span>&lt;span class="o">&amp;lt;&lt;/span>&lt;span class="n">Node&lt;/span>&lt;span class="o">&amp;gt;&lt;/span> &lt;span class="n">prev&lt;/span>&lt;span class="p">;&lt;/span> &lt;span class="c1">// 使用 weak_ptr 以防止循環參照
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">Node&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="k">const&lt;/span> &lt;span class="n">std&lt;/span>&lt;span class="o">::&lt;/span>&lt;span class="n">string&lt;/span>&lt;span class="o">&amp;amp;&lt;/span> &lt;span class="n">n&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="o">:&lt;/span> &lt;span class="n">name&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">n&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">{&lt;/span> &lt;span class="n">std&lt;/span>&lt;span class="o">::&lt;/span>&lt;span class="n">cout&lt;/span> &lt;span class="o">&amp;lt;&amp;lt;&lt;/span> &lt;span class="s">&amp;#34;Created &amp;#34;&lt;/span> &lt;span class="o">&amp;lt;&amp;lt;&lt;/span> &lt;span class="n">name&lt;/span> &lt;span class="o">&amp;lt;&amp;lt;&lt;/span> &lt;span class="s">&amp;#34;&lt;/span>&lt;span class="se">\n&lt;/span>&lt;span class="s">&amp;#34;&lt;/span>&lt;span class="p">;&lt;/span> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="o">~&lt;/span>&lt;span class="n">Node&lt;/span>&lt;span class="p">()&lt;/span> &lt;span class="p">{&lt;/span> &lt;span class="n">std&lt;/span>&lt;span class="o">::&lt;/span>&lt;span class="n">cout&lt;/span> &lt;span class="o">&amp;lt;&amp;lt;&lt;/span> &lt;span class="s">&amp;#34;Destroyed &amp;#34;&lt;/span> &lt;span class="o">&amp;lt;&amp;lt;&lt;/span> &lt;span class="n">name&lt;/span> &lt;span class="o">&amp;lt;&amp;lt;&lt;/span> &lt;span class="s">&amp;#34;&lt;/span>&lt;span class="se">\n&lt;/span>&lt;span class="s">&amp;#34;&lt;/span>&lt;span class="p">;&lt;/span> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">};&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kt">int&lt;/span> &lt;span class="nf">main&lt;/span>&lt;span class="p">()&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">auto&lt;/span> &lt;span class="n">nodeA&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">std&lt;/span>&lt;span class="o">::&lt;/span>&lt;span class="n">make_shared&lt;/span>&lt;span class="o">&amp;lt;&lt;/span>&lt;span class="n">Node&lt;/span>&lt;span class="o">&amp;gt;&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#34;A&amp;#34;&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">auto&lt;/span> &lt;span class="n">nodeB&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">std&lt;/span>&lt;span class="o">::&lt;/span>&lt;span class="n">make_shared&lt;/span>&lt;span class="o">&amp;lt;&lt;/span>&lt;span class="n">Node&lt;/span>&lt;span class="o">&amp;gt;&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#34;B&amp;#34;&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">nodeA&lt;/span>&lt;span class="o">-&amp;gt;&lt;/span>&lt;span class="n">next&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">nodeB&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">nodeB&lt;/span>&lt;span class="o">-&amp;gt;&lt;/span>&lt;span class="n">prev&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">nodeA&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1">// 從 weak_ptr 取得 shared_ptr 來進行存取
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="k">if&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="k">auto&lt;/span> &lt;span class="n">locked_prev&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">nodeB&lt;/span>&lt;span class="o">-&amp;gt;&lt;/span>&lt;span class="n">prev&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">lock&lt;/span>&lt;span class="p">())&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">std&lt;/span>&lt;span class="o">::&lt;/span>&lt;span class="n">cout&lt;/span> &lt;span class="o">&amp;lt;&amp;lt;&lt;/span> &lt;span class="s">&amp;#34;Node B&amp;#39;s prev is &amp;#34;&lt;/span> &lt;span class="o">&amp;lt;&amp;lt;&lt;/span> &lt;span class="n">locked_prev&lt;/span>&lt;span class="o">-&amp;gt;&lt;/span>&lt;span class="n">name&lt;/span> &lt;span class="o">&amp;lt;&amp;lt;&lt;/span> &lt;span class="s">&amp;#34;&lt;/span>&lt;span class="se">\n&lt;/span>&lt;span class="s">&amp;#34;&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span> &lt;span class="k">else&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">std&lt;/span>&lt;span class="o">::&lt;/span>&lt;span class="n">cout&lt;/span> &lt;span class="o">&amp;lt;&amp;lt;&lt;/span> &lt;span class="s">&amp;#34;Node B&amp;#39;s prev is already destroyed.&lt;/span>&lt;span class="se">\n&lt;/span>&lt;span class="s">&amp;#34;&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="mi">0&lt;/span>&lt;span class="p">;&lt;/span> &lt;span class="c1">// nodeA 與 nodeB 都會被適當地銷毀
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span>&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;h2 id="5-多執行緒環境下的共享所有權限制">5. 多執行緒環境下的共享所有權限制
&lt;/h2>&lt;p>關於 &lt;code>shared_ptr&lt;/code> 的執行緒安全性 (Thread Safety) 很容易被誤解：「控制區塊內參照計數的更新是執行緒安全的」，但是「&lt;code>shared_ptr&lt;/code> 物件本身的讀寫並非執行緒安全」。&lt;/p>
&lt;ul>
&lt;li>&lt;strong>安全的操作&lt;/strong>：多個執行緒讀寫 &lt;em>各自獨立的&lt;/em> &lt;code>shared_ptr&lt;/code> 實例 (但共享同一個控制區塊)。&lt;/li>
&lt;li>&lt;strong>資料競爭 (危險)&lt;/strong>：多個執行緒同時讀寫 &lt;em>完全相同的&lt;/em> &lt;code>shared_ptr&lt;/code> 實例。&lt;/li>
&lt;/ul>
&lt;p>如果必須在多個執行緒之間共享同一個實例，則需要使用 &lt;code>std::atomic&amp;lt;std::shared_ptr&amp;lt;T&amp;gt;&amp;gt;&lt;/code> (C++20)，或是使用互斥鎖 (&lt;code>std::mutex&lt;/code>) 進行保護。&lt;/p>
&lt;h2 id="6-參照計數的數學公式化">6. 參照計數的數學公式化
&lt;/h2>&lt;p>將控制區塊中生命週期的狀態轉換以數學方式表示如下：
假設時間 $t$ 時的強參照計數為 $S(t)$，弱參照計數為 $W(t)$。&lt;/p>
&lt;p>初始狀態 (&lt;code>make_shared&lt;/code> 剛執行完後)：
&lt;/p>
$$ S(0) = 1, \quad W(0) = 0 $$
&lt;p>當進行複製 (&lt;code>shared_ptr&lt;/code> 的複製) 時：
&lt;/p>
$$ S(t_{next}) = S(t) + 1 $$
&lt;p>被管理的物件 (Managed Object) 被銷毀的條件：
&lt;/p>
$$ \lim_{t \to t_d} S(t) = 0 $$
&lt;p>控制區塊 (Control Block) 本身從記憶體中被釋放的條件：
&lt;/p>
$$ S(t) = 0 \quad \land \quad W(t) = 0 $$
&lt;p>
也就是說，
&lt;/p>
$$ S(t) + W(t) = 0 $$
&lt;p>正如這個數學式所顯示的，只要 &lt;code>weak_ptr&lt;/code> 繼續存在 ($W(t) > 0$)，即使被管理的物件已經被銷毀，用於控制區塊的微小記憶體空間仍會持續保留。這在某些情況下會成為 &lt;code>make_shared&lt;/code> 唯一的缺點 (由於被管理物件的記憶體與控制區塊是一體化的，如果有弱參照殘留，那麼用於被管理物件的龐大記憶體空間也不會歸還給系統)，但通常 &lt;code>make_shared&lt;/code> 在效能上的優勢會帶來壓倒性的好處。&lt;/p>
&lt;h2 id="7-結論">7. 結論
&lt;/h2>&lt;p>在 Modern C++ 中的記憶體管理，已經不再是手動管理 &lt;code>new&lt;/code>/&lt;code>delete&lt;/code> 的時代了。&lt;/p>
&lt;ol>
&lt;li>預設情況下應總是使用 &lt;strong>&lt;code>std::unique_ptr&lt;/code>&lt;/strong>，在享受零開銷恩惠的同時，將明確的所有權納入設計之中。&lt;/li>
&lt;li>只有在真正需要於多個擁有者之間共享生命週期時，才使用 &lt;strong>&lt;code>std::shared_ptr&lt;/code>&lt;/strong>，並且使用 &lt;code>std::make_shared&lt;/code> 來建立。&lt;/li>
&lt;li>在可能會發生共享循環 (循環參照) 的資料結構，或是在實作觀察者模式 (Observer Pattern) 時，活用 &lt;strong>&lt;code>std::weak_ptr&lt;/code>&lt;/strong> 來防範記憶體外洩於未然。&lt;/li>
&lt;/ol>
&lt;p>深入理解智慧指標並在適當的地方活用它們，將能在完全不犧牲 C++ 效能的情況下，建構出安全且堅固的軟體架構。&lt;/p></description></item></channel></rss>