<?xml version="1.0" encoding="utf-8"?><feed xmlns="http://www.w3.org/2005/Atom" ><generator uri="https://jekyllrb.com/" version="3.10.0">Jekyll</generator><link href="https://mikami-w.github.io/feed.xml" rel="self" type="application/atom+xml" /><link href="https://mikami-w.github.io/" rel="alternate" type="text/html" /><updated>2025-12-07T09:34:41+00:00</updated><id>https://mikami-w.github.io/feed.xml</id><title type="html">三上のBlog</title><subtitle>递归树状的知识使我爱恨交织.</subtitle><author><name>Mikami</name></author><entry><title type="html">C++ 移动语义避坑指南: 为什么 T&amp;amp;&amp;amp; 是左值? std::move 与 std::forward 该怎么选?</title><link href="https://mikami-w.github.io/cpp/2025/12/07/C++-%E7%A7%BB%E5%8A%A8%E8%AF%AD%E4%B9%89%E9%81%BF%E5%9D%91%E6%8C%87%E5%8D%97-%E4%B8%BA%E4%BB%80%E4%B9%88-T&&-%E6%98%AF%E5%B7%A6%E5%80%BC-std_move-%E4%B8%8E-std_forward-%E8%AF%A5%E6%80%8E%E4%B9%88%E9%80%89.html" rel="alternate" type="text/html" title="C++ 移动语义避坑指南: 为什么 T&amp;amp;&amp;amp; 是左值? std::move 与 std::forward 该怎么选?" /><published>2025-12-07T00:00:00+00:00</published><updated>2025-12-07T00:00:00+00:00</updated><id>https://mikami-w.github.io/cpp/2025/12/07/C++%20%E7%A7%BB%E5%8A%A8%E8%AF%AD%E4%B9%89%E9%81%BF%E5%9D%91%E6%8C%87%E5%8D%97%EF%BC%9A%E4%B8%BA%E4%BB%80%E4%B9%88%20T&amp;&amp;%20%E6%98%AF%E5%B7%A6%E5%80%BC%EF%BC%9Fstd_move%20%E4%B8%8E%20std_forward%20%E8%AF%A5%E6%80%8E%E4%B9%88%E9%80%89%EF%BC%9F</id><content type="html" xml:base="https://mikami-w.github.io/cpp/2025/12/07/C++-%E7%A7%BB%E5%8A%A8%E8%AF%AD%E4%B9%89%E9%81%BF%E5%9D%91%E6%8C%87%E5%8D%97-%E4%B8%BA%E4%BB%80%E4%B9%88-T&amp;&amp;-%E6%98%AF%E5%B7%A6%E5%80%BC-std_move-%E4%B8%8E-std_forward-%E8%AF%A5%E6%80%8E%E4%B9%88%E9%80%89.html"><![CDATA[<p>在现代 C++ 开发中，移动语义 (Move Semantics) 是高性能编程的基石. 然而，即使是有经验的开发者，也常常会在 <code class="language-plaintext highlighter-rouge">T&amp;&amp;</code>, <code class="language-plaintext highlighter-rouge">std::move</code> 和 <code class="language-plaintext highlighter-rouge">std::forward</code> 的使用上栽跟头.</p>

<p>你是否遇到过这样的疑惑: <strong>明明函数参数类型写的是 <code class="language-plaintext highlighter-rouge">T&amp;&amp;</code> (右值引用) ，为什么编译器还是调用了拷贝构造函数? ** 或者，</strong>写模板时到底该用 <code class="language-plaintext highlighter-rouge">move</code> 还是 <code class="language-plaintext highlighter-rouge">forward</code>? **</p>

<p>本文将基于实战场景，总结 C++ 移动语义中最核心的规则与陷阱.</p>

<hr />

<h2 id="stdmove-问题引入"><code class="language-plaintext highlighter-rouge">std::move</code> 问题引入</h2>

<p>让我们考虑以下问题:</p>

<blockquote>
  <div class="language-cpp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">BASIC_JSON_TEMPLATE</span> 
<span class="kt">void</span> <span class="n">BASIC_JSON_TYPE</span><span class="o">::</span><span class="n">push_back</span><span class="p">(</span><span class="n">basic_json</span><span class="o">&amp;&amp;</span> <span class="n">val</span><span class="p">)</span> 
<span class="p">{</span> 
 	<span class="n">as_array</span><span class="p">().</span><span class="n">emplace_back</span><span class="p">(</span><span class="n">std</span><span class="o">::</span><span class="n">move</span><span class="p">(</span><span class="n">val</span><span class="p">));</span> 
<span class="p">}</span>
</code></pre></div>  </div>

  <p>在这段代码中, std::move是否必要? 如果只传入<code class="language-plaintext highlighter-rouge">val</code>效果是否相同?</p>
</blockquote>

<p>简短的回答是: **<code class="language-plaintext highlighter-rouge">std::move</code> 是绝对必要的. 如果去掉它，效果完全不同. **</p>

<p>虽然函数参数 <code class="language-plaintext highlighter-rouge">val</code> 的<strong>类型</strong>是 <code class="language-plaintext highlighter-rouge">basic_json&amp;&amp;</code> (右值引用) ，但在函数体内部，<code class="language-plaintext highlighter-rouge">val</code> 这个<strong>变量本身</strong>是有名字的.</p>

<p>在 C++ 中有一条核心规则: **具名变量 (Named Variable) 永远是左值 (Lvalue) ，即使它的类型是右值引用 (Rvalue Reference) . **</p>

<p>这是一个非常深刻且容易混淆的概念，触及了 C++ 中 <strong>类型 (Type)</strong> 与 <strong>值类别 (Value Category)</strong> 之间的核心区别.</p>

<h3 id="核心矛盾-类型-vs-身份">核心矛盾: 类型 vs. 身份</h3>

<p>此时的困惑在于混淆了以下两个概念:</p>

<ol>
  <li><strong>变量的类型</strong>: <code class="language-plaintext highlighter-rouge">val</code> 的类型是 <code class="language-plaintext highlighter-rouge">basic_json&amp;&amp;</code> (右值引用) . 这意味着它<strong>指向</strong>一个右值 (临时的, 可移动的对象) .</li>
  <li><strong>变量的表达式属性 (值类别) **: 在函数体内部，<code class="language-plaintext highlighter-rouge">val</code> 这个</strong>名字本身**是一个左值.</li>
</ol>

<p>**为什么? ** 因为 <code class="language-plaintext highlighter-rouge">val</code> 在这个函数体内是有名字的，它是“持久”存在的. 你可以在第一行用它，在第二行用它，在第三行还用它.</p>

<ul>
  <li><strong>只要你有名字，你就是左值. ** 编译器认为你可能在后面还会用到它，所以默认</strong>不给你移动<strong>，而是</strong>拷贝** (因为拷贝是安全的，原对象还在) .</li>
  <li>如果你确定“我这行代码用完就不再用它了”，你必须<strong>显式地</strong>使用 <code class="language-plaintext highlighter-rouge">std::move(val)</code>. 这相当于你签了一份“免责声明”，告诉编译器: “我知道我在做什么，把它转成右值吧，后面坏了我负责. ”</li>
</ul>

<p>也就是说:</p>

<ul>
  <li><strong>类型是 <code class="language-plaintext highlighter-rouge">T&amp;&amp;</code></strong> 只代表“它<strong>可以</strong>被移动”.</li>
  <li><strong>名字本身</strong> 代表“它在当前作用域还活着”.</li>
  <li>要想真正触发移动，必须用 <code class="language-plaintext highlighter-rouge">std::move()</code> 剥夺它的名字属性，将其转化为一个<strong>亡值 (Xvalue)</strong>, 即一个临时的右值表达式.</li>
</ul>

<p>无论它的类型写得多么花哨 (比如 <code class="language-plaintext highlighter-rouge">T&amp;&amp;</code>, <code class="language-plaintext highlighter-rouge">const T&amp;&amp;</code>) ，只要你在函数体里能用名字 <code class="language-plaintext highlighter-rouge">val</code> 访问它，它就是左值. 因为编译器认为: “既然你有名字，说明你还要被使用，所以我不能背着你偷偷把你移走”.</p>

<h2 id="何时显式调用stdmove">何时显式调用<code class="language-plaintext highlighter-rouge">std::move</code>?</h2>

<p>只需记住以下黄金法则:</p>

<p><strong>除编译器对函数内局部变量的返回值优化(RVO)的场景外, 所有想要移动一个具名变量或引用的场景都应该显式调用<code class="language-plaintext highlighter-rouge">std::move</code>.</strong></p>

<p>我们可以把场景分为两类: <strong>传递</strong> 和 <strong>返回</strong>.</p>

<h3 id="场景-a-传递-passing--必须-move">场景 A: 传递 (Passing) —— 必须 move</h3>

<p>当你手里有一个具名的对象 (无论是局部变量还是参数) ，你想把它<strong>交给</strong>另一个函数 (如 <code class="language-plaintext highlighter-rouge">vector::push_back</code>) ，并且你不再需要它了:</p>

<ul>
  <li><strong>必须显示调用 <code class="language-plaintext highlighter-rouge">std::move</code></strong>.</li>
  <li>这就是你刚才遇到的 <code class="language-plaintext highlighter-rouge">push_back(val)</code> vs <code class="language-plaintext highlighter-rouge">push_back(std::move(val))</code> 的情况.</li>
</ul>

<h3 id="场景-b-返回-returning--不要-move-绝大多数情况">场景 B: 返回 (Returning) —— 不要 move (绝大多数情况)</h3>

<p>当你返回一个函数内的<strong>局部变量</strong>时:</p>

<div class="language-cpp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">basic_json</span> <span class="nf">make_json</span><span class="p">()</span> <span class="p">{</span>
    <span class="n">basic_json</span> <span class="n">j</span><span class="p">;</span>
    <span class="c1">// ... 对 j 进行操作 ...</span>
    
    <span class="k">return</span> <span class="n">j</span><span class="p">;</span> <span class="c1">// 不要写 std::move(j)!</span>
<span class="p">}</span>
</code></pre></div></div>

<ul>
  <li><strong>RVO/NRVO (返回值优化)</strong>: 编译器会直接在调用者的栈上构造 <code class="language-plaintext highlighter-rouge">j</code>，完全消除拷贝和移动. 这是最高效的 (Zero Copy/Move) .</li>
  <li><strong>隐式移动 (Implicit Move)</strong>: 即使编译器太笨无法进行 RVO，C++ 标准也规定: <strong>如果返回的是局部对象，编译器必须自动把它视为右值去尝试移动</strong>.</li>
  <li><strong>画蛇添足</strong>: 如果你写了 <code class="language-plaintext highlighter-rouge">return std::move(j);</code>，你反而<strong>破坏</strong>了 RVO 的条件，强制编译器放弃“零拷贝”，转而去执行一次“移动构造”. 虽然移动很快，但 RVO 是什么都不做，显然 RVO 更快.</li>
</ul>

<h4 id="特例-当返回的是函数参数时此时需要显式-move">特例: 当返回的是函数参数时——此时需要显式 move</h4>

<p>有一个场景虽然是“返回”，但<strong>不能</strong>依赖 RVO，需要显式 <code class="language-plaintext highlighter-rouge">std::move</code>: <strong>当返回的是函数参数时</strong> (特别是右值引用类型的参数) .</p>

<div class="language-cpp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// push_back 场景是把参数传递给另一个函数 -&gt; 需要 move (正确)</span>
<span class="kt">void</span> <span class="nf">push_back</span><span class="p">(</span><span class="n">basic_json</span><span class="o">&amp;&amp;</span> <span class="n">val</span><span class="p">)</span> <span class="p">{</span>
    <span class="n">vec</span><span class="p">.</span><span class="n">emplace_back</span><span class="p">(</span><span class="n">std</span><span class="o">::</span><span class="n">move</span><span class="p">(</span><span class="n">val</span><span class="p">));</span> 
<span class="p">}</span>

<span class="c1">// 如果你的场景是直接把这个参数“原路返回” -&gt; 也需要 move</span>
<span class="n">basic_json</span> <span class="n">pass_through</span><span class="p">(</span><span class="n">basic_json</span><span class="o">&amp;&amp;</span> <span class="n">val</span><span class="p">)</span> <span class="p">{</span>
    <span class="c1">// return val;            // 错误！val 是左值，会触发拷贝构造 (如果 basic_json 有拷贝构造的话) </span>
    <span class="k">return</span> <span class="n">std</span><span class="o">::</span><span class="n">move</span><span class="p">(</span><span class="n">val</span><span class="p">);</span>    <span class="c1">// 正确！参数不是局部变量，无法触发 NRVO，必须显式 move</span>
<span class="p">}</span>
</code></pre></div></div>

<h2 id="总结">总结</h2>

<table>
  <thead>
    <tr>
      <th><strong>场景</strong></th>
      <th><strong>val 的身份</strong></th>
      <th><strong>是否需要 std::move(val)?</strong></th>
      <th><strong>原因</strong></th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td><strong>传参</strong> 给别的函数</td>
      <td><code class="language-plaintext highlighter-rouge">basic_json&amp;&amp; val</code> (参数)</td>
      <td><strong>需要</strong></td>
      <td>名字是左值，需转为右值以触发接手函数的移动语义</td>
    </tr>
    <tr>
      <td><strong>传参</strong> 给别的函数</td>
      <td><code class="language-plaintext highlighter-rouge">basic_json val</code> (局部变量)</td>
      <td><strong>需要</strong></td>
      <td>名字是左值，需转为右值以移交所有权</td>
    </tr>
    <tr>
      <td><strong>返回</strong> 给调用者</td>
      <td><code class="language-plaintext highlighter-rouge">basic_json val</code> (局部变量)</td>
      <td><strong>不要</strong></td>
      <td>阻碍 RVO，且编译器会自动处理隐式移动</td>
    </tr>
    <tr>
      <td><strong>返回</strong> 给调用者</td>
      <td><code class="language-plaintext highlighter-rouge">basic_json&amp;&amp; val</code> (参数)</td>
      <td><strong>需要</strong></td>
      <td>参数不是局部变量，无法触发 NRVO，需显式转为右值</td>
    </tr>
  </tbody>
</table>

<h2 id="stdmove-的实现"><code class="language-plaintext highlighter-rouge">std::move</code> 的实现</h2>

<div class="language-cpp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">template</span><span class="o">&lt;</span><span class="k">typename</span> <span class="nc">_Tp</span><span class="p">&gt;</span>
<span class="n">_GLIBCXX_NODISCARD</span>
<span class="k">constexpr</span> <span class="k">typename</span> <span class="n">std</span><span class="o">::</span><span class="n">remove_reference</span><span class="o">&lt;</span><span class="n">_Tp</span><span class="o">&gt;::</span><span class="n">type</span><span class="o">&amp;&amp;</span>
<span class="n">move</span><span class="p">(</span><span class="n">_Tp</span><span class="o">&amp;&amp;</span> <span class="n">__t</span><span class="p">)</span> <span class="k">noexcept</span>
<span class="p">{</span> 
    <span class="k">return</span> <span class="k">static_cast</span><span class="o">&lt;</span><span class="k">typename</span> <span class="n">std</span><span class="o">::</span><span class="n">remove_reference</span><span class="o">&lt;</span><span class="n">_Tp</span><span class="o">&gt;::</span><span class="n">type</span><span class="o">&amp;&amp;&gt;</span><span class="p">(</span><span class="n">__t</span><span class="p">);</span>
<span class="p">}</span>
</code></pre></div></div>

<h2 id="stdforward-引入-为什么我们需要-stdforward--痛点"><code class="language-plaintext highlighter-rouge">std::forward </code>引入: 为什么我们需要 <code class="language-plaintext highlighter-rouge">std::forward</code>?  (痛点)</h2>

<p>假设你写了一个泛型函数 <code class="language-plaintext highlighter-rouge">wrapper</code>，它的作用只是把参数透传给另一个函数 <code class="language-plaintext highlighter-rouge">process</code>.</p>

<div class="language-cpp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kt">void</span> <span class="nf">process</span><span class="p">(</span><span class="kt">int</span><span class="o">&amp;</span> <span class="n">x</span><span class="p">)</span>  <span class="p">{</span> <span class="n">std</span><span class="o">::</span><span class="n">cout</span> <span class="o">&lt;&lt;</span> <span class="s">"处理左值 (Copy)"</span> <span class="o">&lt;&lt;</span> <span class="n">std</span><span class="o">::</span><span class="n">endl</span><span class="p">;</span> <span class="p">}</span>
<span class="kt">void</span> <span class="n">process</span><span class="p">(</span><span class="kt">int</span><span class="o">&amp;&amp;</span> <span class="n">x</span><span class="p">)</span> <span class="p">{</span> <span class="n">std</span><span class="o">::</span><span class="n">cout</span> <span class="o">&lt;&lt;</span> <span class="s">"处理右值 (Move)"</span> <span class="o">&lt;&lt;</span> <span class="n">std</span><span class="o">::</span><span class="n">endl</span><span class="p">;</span> <span class="p">}</span>

<span class="k">template</span> <span class="o">&lt;</span><span class="k">typename</span> <span class="nc">T</span><span class="p">&gt;</span>
<span class="kt">void</span> <span class="n">wrapper</span><span class="p">(</span><span class="n">T</span><span class="o">&amp;&amp;</span> <span class="n">arg</span><span class="p">)</span> <span class="p">{</span>
    <span class="c1">// arg 在这里有名字，所以它是左值！</span>
    <span class="c1">// 无论外面传入的是什么，这里调用的永远是 process(int&amp;)</span>
    <span class="n">process</span><span class="p">(</span><span class="n">arg</span><span class="p">);</span> 
<span class="p">}</span>

<span class="kt">int</span> <span class="n">main</span><span class="p">()</span> <span class="p">{</span>
    <span class="kt">int</span> <span class="n">a</span> <span class="o">=</span> <span class="mi">1</span><span class="p">;</span>
    <span class="n">wrapper</span><span class="p">(</span><span class="n">a</span><span class="p">);</span> <span class="c1">// 传入左值 -&gt; 期望调用左值版本 -&gt; 实际调用左值版本 (OK)</span>
    <span class="n">wrapper</span><span class="p">(</span><span class="mi">1</span><span class="p">);</span> <span class="c1">// 传入右值 -&gt; 期望调用右值版本 -&gt; 实际调用左值版本 (性能损失!)</span>
<span class="p">}</span>
</code></pre></div></div>

<p><strong>问题在于</strong>: 正如我们在上一个问题中讨论的，参数 <code class="language-plaintext highlighter-rouge">arg</code> 虽然类型可能是 <code class="language-plaintext highlighter-rouge">int&amp;&amp;</code>，但因为它有名字，在 <code class="language-plaintext highlighter-rouge">wrapper</code> 内部它变成了左值. 如果我们不做处理直接传给 <code class="language-plaintext highlighter-rouge">process</code>，右值的信息就丢失了，永远无法触发 <code class="language-plaintext highlighter-rouge">process</code> 的移动语义版本.</p>

<p>**尝试用 <code class="language-plaintext highlighter-rouge">std::move</code> 修复? ** 如果写成 <code class="language-plaintext highlighter-rouge">process(std::move(arg))</code>，那么当你传入左值 <code class="language-plaintext highlighter-rouge">a</code> 时，<code class="language-plaintext highlighter-rouge">wrapper</code> 也会强制把它 move 掉. 这很危险 (外部的 <code class="language-plaintext highlighter-rouge">a</code> 可能被意外掏空) .</p>

<p>我们需要一种机制，能够<strong>“完美地”</strong>还原参数最原始的属性 (是左值就传左值，是右值就传右值) . 这就是 <strong>完美转发 (Perfect Forwarding)</strong>.</p>

<h2 id="stdforward-的工作原理及实现"><code class="language-plaintext highlighter-rouge">std::forward</code> 的工作原理及实现</h2>

<p><code class="language-plaintext highlighter-rouge">std::forward</code> 配合 <strong>万能引用 (Universal Reference)</strong> (即模板中的 <code class="language-plaintext highlighter-rouge">T&amp;&amp;</code>) 使用.</p>

<div class="language-cpp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="cm">/**
*  @brief  Forward an lvalue.
*  @return The parameter cast to the specified type.
*
*  This function is used to implement "perfect forwarding".
*/</span>
<span class="k">template</span><span class="o">&lt;</span><span class="k">typename</span> <span class="nc">_Tp</span><span class="p">&gt;</span>
<span class="n">_GLIBCXX_NODISCARD</span>
<span class="k">constexpr</span> <span class="n">_Tp</span><span class="o">&amp;&amp;</span>
<span class="n">forward</span><span class="p">(</span><span class="k">typename</span> <span class="n">std</span><span class="o">::</span><span class="n">remove_reference</span><span class="o">&lt;</span><span class="n">_Tp</span><span class="o">&gt;::</span><span class="n">type</span><span class="o">&amp;</span> <span class="n">__t</span><span class="p">)</span> <span class="k">noexcept</span>
<span class="p">{</span> 
    <span class="k">return</span> <span class="k">static_cast</span><span class="o">&lt;</span><span class="n">_Tp</span><span class="o">&amp;&amp;&gt;</span><span class="p">(</span><span class="n">__t</span><span class="p">);</span>
<span class="p">}</span>

<span class="cm">/**
*  @brief  Forward an rvalue.
*  @return The parameter cast to the specified type.
*
*  This function is used to implement "perfect forwarding".
*/</span>
<span class="k">template</span><span class="o">&lt;</span><span class="k">typename</span> <span class="nc">_Tp</span><span class="p">&gt;</span>
<span class="n">_GLIBCXX_NODISCARD</span>
<span class="k">constexpr</span> <span class="n">_Tp</span><span class="o">&amp;&amp;</span>
<span class="n">forward</span><span class="p">(</span><span class="k">typename</span> <span class="n">std</span><span class="o">::</span><span class="n">remove_reference</span><span class="o">&lt;</span><span class="n">_Tp</span><span class="o">&gt;::</span><span class="n">type</span><span class="o">&amp;&amp;</span> <span class="n">__t</span><span class="p">)</span> <span class="k">noexcept</span>
<span class="p">{</span>
    <span class="k">static_assert</span><span class="p">(</span><span class="o">!</span><span class="n">std</span><span class="o">::</span><span class="n">is_lvalue_reference</span><span class="o">&lt;</span><span class="n">_Tp</span><span class="o">&gt;::</span><span class="n">value</span><span class="p">,</span>
    <span class="s">"std::forward must not be used to convert an rvalue to an lvalue"</span><span class="p">);</span>
    <span class="c1">// 上面这行代码在阻止你做一件蠢事: 试图把一个右值当作左值转发. </span>
    <span class="c1">// 例如: `std::forward&lt;int&amp;&gt;(1)` -&gt; 导致悬垂引用风险(左值引用实际引用了一个右值)!</span>
    <span class="k">return</span> <span class="k">static_cast</span><span class="o">&lt;</span><span class="n">_Tp</span><span class="o">&amp;&amp;&gt;</span><span class="p">(</span><span class="n">__t</span><span class="p">);</span>
<span class="p">}</span>
</code></pre></div></div>

<p>上述实现中, 第一个重载版本是在写泛型函数时 99% 遇到的情况 (即转发一个左值 (不要忘了, <strong>右值引用也是左值!</strong>) ); 第二个重载版本允许了 <code class="language-plaintext highlighter-rouge">std::forward&lt;int&gt;(1)</code> 或 <code class="language-plaintext highlighter-rouge">std::forward&lt;T&gt;(std::move(arg))</code> 的使用, 实际使用较少.</p>

<h3 id="引用折叠规则">引用折叠规则</h3>

<p><code class="language-plaintext highlighter-rouge">std::forward</code> 的实现依赖于 C++ 的 <strong>引用折叠 (Reference Collapsing)</strong> 规则:</p>

<ol>
  <li><strong><code class="language-plaintext highlighter-rouge">T&amp; &amp;</code> → <code class="language-plaintext highlighter-rouge">T&amp;</code></strong> (左值引用 + 左值引用 → 左值引用)</li>
  <li><strong><code class="language-plaintext highlighter-rouge">T&amp; &amp;&amp;</code> → <code class="language-plaintext highlighter-rouge">T&amp;</code></strong> (左值引用 + 右值引用 → 左值引用)</li>
  <li><strong><code class="language-plaintext highlighter-rouge">T&amp;&amp; &amp;</code> → <code class="language-plaintext highlighter-rouge">T&amp;</code></strong> (右值引用 + 左值引用 → 左值引用)</li>
  <li><strong><code class="language-plaintext highlighter-rouge">T&amp;&amp; &amp;&amp;</code> → <code class="language-plaintext highlighter-rouge">T&amp;&amp;</code></strong> (右值引用 + 右值引用 → 右值引用)</li>
</ol>

<p>简单理解:</p>

<ul>
  <li><strong>“只要有左值引用，就折叠成左值引用”</strong>.</li>
  <li><strong>“只有当所有引用都是右值引用时，结果才是右值引用”</strong>.</li>
</ul>

<h2 id="使用">使用</h2>

<div class="language-cpp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">template</span> <span class="o">&lt;</span><span class="k">typename</span> <span class="nc">T</span><span class="p">&gt;</span>
<span class="kt">void</span> <span class="nf">wrapper</span><span class="p">(</span><span class="n">T</span><span class="o">&amp;&amp;</span> <span class="n">arg</span><span class="p">)</span> <span class="p">{</span>
    <span class="c1">// 这里的 T 会保存原始参数的左值/右值信息</span>
    <span class="n">process</span><span class="p">(</span><span class="n">std</span><span class="o">::</span><span class="n">forward</span><span class="o">&lt;</span><span class="n">T</span><span class="o">&gt;</span><span class="p">(</span><span class="n">arg</span><span class="p">));</span> 
<span class="p">}</span>
</code></pre></div></div>

<ol>
  <li><strong>传入左值 <code class="language-plaintext highlighter-rouge">wrapper(a)</code></strong>:
    <ul>
      <li><code class="language-plaintext highlighter-rouge">T</code> 被推导为 <code class="language-plaintext highlighter-rouge">int&amp;</code>.</li>
      <li><code class="language-plaintext highlighter-rouge">std::forward&lt;int&amp;&gt;(arg)</code> 会返回 <code class="language-plaintext highlighter-rouge">int&amp;</code> (左值).</li>
      <li><code class="language-plaintext highlighter-rouge">process</code> 接收到左值.</li>
    </ul>
  </li>
  <li><strong>传入右值 <code class="language-plaintext highlighter-rouge">wrapper(1)</code></strong>:
    <ul>
      <li><code class="language-plaintext highlighter-rouge">T</code> 被推导为 <code class="language-plaintext highlighter-rouge">int</code> (或者 <code class="language-plaintext highlighter-rouge">int&amp;&amp;</code>，取决于编译器实现细节，但效果一致).</li>
      <li><code class="language-plaintext highlighter-rouge">std::forward&lt;int&gt;(arg)</code> 会返回 <code class="language-plaintext highlighter-rouge">int&amp;&amp;</code> (右值).</li>
      <li><code class="language-plaintext highlighter-rouge">process</code> 接收到右值.</li>
    </ul>
  </li>
</ol>

<h3 id="代码对比演示">代码对比演示</h3>

<div class="language-cpp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="cp">#include</span> <span class="cpf">&lt;iostream&gt;</span><span class="cp">
#include</span> <span class="cpf">&lt;utility&gt;</span><span class="c1"> // for std::move, std::forward</span><span class="cp">
</span>
<span class="c1">// 目标函数</span>
<span class="kt">void</span> <span class="nf">run</span><span class="p">(</span><span class="kt">int</span><span class="o">&amp;</span> <span class="n">x</span><span class="p">)</span>  <span class="p">{</span> <span class="n">std</span><span class="o">::</span><span class="n">cout</span> <span class="o">&lt;&lt;</span> <span class="s">"Lvalue ref"</span> <span class="o">&lt;&lt;</span> <span class="n">std</span><span class="o">::</span><span class="n">endl</span><span class="p">;</span> <span class="p">}</span>
<span class="kt">void</span> <span class="n">run</span><span class="p">(</span><span class="kt">int</span><span class="o">&amp;&amp;</span> <span class="n">x</span><span class="p">)</span> <span class="p">{</span> <span class="n">std</span><span class="o">::</span><span class="n">cout</span> <span class="o">&lt;&lt;</span> <span class="s">"Rvalue ref"</span> <span class="o">&lt;&lt;</span> <span class="n">std</span><span class="o">::</span><span class="n">endl</span><span class="p">;</span> <span class="p">}</span>

<span class="c1">// 1. 使用 std::move (鲁莽的中介)</span>
<span class="k">template</span> <span class="o">&lt;</span><span class="k">typename</span> <span class="nc">T</span><span class="p">&gt;</span>
<span class="kt">void</span> <span class="n">bad_wrapper</span><span class="p">(</span><span class="n">T</span><span class="o">&amp;&amp;</span> <span class="n">arg</span><span class="p">)</span> <span class="p">{</span>
    <span class="n">run</span><span class="p">(</span><span class="n">std</span><span class="o">::</span><span class="n">move</span><span class="p">(</span><span class="n">arg</span><span class="p">));</span> <span class="c1">// 永远转为右值</span>
<span class="p">}</span>

<span class="c1">// 2. 不做处理 (懒惰的中介)</span>
<span class="k">template</span> <span class="o">&lt;</span><span class="k">typename</span> <span class="nc">T</span><span class="p">&gt;</span>
<span class="kt">void</span> <span class="n">lazy_wrapper</span><span class="p">(</span><span class="n">T</span><span class="o">&amp;&amp;</span> <span class="n">arg</span><span class="p">)</span> <span class="p">{</span>
    <span class="n">run</span><span class="p">(</span><span class="n">arg</span><span class="p">);</span> <span class="c1">// 永远作为左值 (因为 arg 有名字)</span>
<span class="p">}</span>

<span class="c1">// 3. 使用 std::forward (完美的中介)</span>
<span class="k">template</span> <span class="o">&lt;</span><span class="k">typename</span> <span class="nc">T</span><span class="p">&gt;</span>
<span class="kt">void</span> <span class="n">perfect_wrapper</span><span class="p">(</span><span class="n">T</span><span class="o">&amp;&amp;</span> <span class="n">arg</span><span class="p">)</span> <span class="p">{</span>
    <span class="n">run</span><span class="p">(</span><span class="n">std</span><span class="o">::</span><span class="n">forward</span><span class="o">&lt;</span><span class="n">T</span><span class="o">&gt;</span><span class="p">(</span><span class="n">arg</span><span class="p">));</span> <span class="c1">// 还原原始属性</span>
<span class="p">}</span>

<span class="kt">int</span> <span class="n">main</span><span class="p">()</span> <span class="p">{</span>
    <span class="kt">int</span> <span class="n">a</span> <span class="o">=</span> <span class="mi">10</span><span class="p">;</span>

    <span class="n">std</span><span class="o">::</span><span class="n">cout</span> <span class="o">&lt;&lt;</span> <span class="s">"--- Bad Wrapper (std::move) ---"</span> <span class="o">&lt;&lt;</span> <span class="n">std</span><span class="o">::</span><span class="n">endl</span><span class="p">;</span>
    <span class="n">bad_wrapper</span><span class="p">(</span><span class="n">a</span><span class="p">);</span> <span class="c1">// 输出: Rvalue ref (危险！a 被意外移动了)</span>
    <span class="n">bad_wrapper</span><span class="p">(</span><span class="mi">10</span><span class="p">);</span> <span class="c1">// 输出: Rvalue ref (正确)</span>

    <span class="n">std</span><span class="o">::</span><span class="n">cout</span> <span class="o">&lt;&lt;</span> <span class="s">"</span><span class="se">\n</span><span class="s">--- Lazy Wrapper (Nothing) ---"</span> <span class="o">&lt;&lt;</span> <span class="n">std</span><span class="o">::</span><span class="n">endl</span><span class="p">;</span>
    <span class="n">lazy_wrapper</span><span class="p">(</span><span class="n">a</span><span class="p">);</span> <span class="c1">// 输出: Lvalue ref (正确)</span>
    <span class="n">lazy_wrapper</span><span class="p">(</span><span class="mi">10</span><span class="p">);</span> <span class="c1">// 输出: Lvalue ref (错误！本该移动却没移动)</span>

    <span class="n">std</span><span class="o">::</span><span class="n">cout</span> <span class="o">&lt;&lt;</span> <span class="s">"</span><span class="se">\n</span><span class="s">--- Perfect Wrapper (std::forward) ---"</span> <span class="o">&lt;&lt;</span> <span class="n">std</span><span class="o">::</span><span class="n">endl</span><span class="p">;</span>
    <span class="n">perfect_wrapper</span><span class="p">(</span><span class="n">a</span><span class="p">);</span> <span class="c1">// 输出: Lvalue ref (完美)</span>
    <span class="n">perfect_wrapper</span><span class="p">(</span><span class="mi">10</span><span class="p">);</span> <span class="c1">// 输出: Rvalue ref (完美)</span>
<span class="p">}</span>
</code></pre></div></div>

<h1 id="二者各自的使用场所">二者各自的使用场所</h1>

<p>这是区分它们的最佳记忆法:</p>

<ul>
  <li><strong><code class="language-plaintext highlighter-rouge">std::move</code></strong>: 用于 <strong>具体类型</strong> 的对象 (Concrete Types) .
    <ul>
      <li>当你明确知道“这个变量以后我不用了，我要把它移走”时使用.</li>
      <li>常见场景: <code class="language-plaintext highlighter-rouge">push_back(std::move(obj))</code>, <code class="language-plaintext highlighter-rouge">return std::move(res)</code> (针对参数).</li>
      <li>意义: *我要处理掉它. *</li>
    </ul>
  </li>
  <li><strong><code class="language-plaintext highlighter-rouge">std::forward</code></strong>: 用于 <strong>模板类型</strong> 的 <strong>万能引用</strong> (<code class="language-plaintext highlighter-rouge">T&amp;&amp;</code>).
    <ul>
      <li>当你是一个“中间商”，不仅要传递数据，还要保留数据的“左/右值属性”给下游函数时使用.</li>
      <li>常见场景: <code class="language-plaintext highlighter-rouge">emplace_back</code>, <code class="language-plaintext highlighter-rouge">make_unique</code>, 函数包装器.</li>
      <li>意义: *我只是帮人传个话，原话照搬. *</li>
    </ul>
  </li>
</ul>]]></content><author><name>Mikami</name></author><category term="cpp" /><category term="cpp" /><category term="code" /><summary type="html"><![CDATA[在现代 C++ 开发中，移动语义 (Move Semantics) 是高性能编程的基石. 然而，即使是有经验的开发者，也常常会在 T&amp;&amp;, std::move 和 std::forward 的使用上栽跟头.]]></summary></entry><entry><title type="html">深入 C++ 模板：解构”模板模板参数” (Template Template Parameters)</title><link href="https://mikami-w.github.io/cpp/2025/11/07/%E6%B7%B1%E5%85%A5C++%E6%A8%A1%E6%9D%BF_%E8%A7%A3%E6%9E%84%E6%A8%A1%E6%9D%BF%E6%A8%A1%E6%9D%BF%E5%8F%82%E6%95%B0.html" rel="alternate" type="text/html" title="深入 C++ 模板：解构”模板模板参数” (Template Template Parameters)" /><published>2025-11-07T00:00:00+00:00</published><updated>2025-11-07T00:00:00+00:00</updated><id>https://mikami-w.github.io/cpp/2025/11/07/%E6%B7%B1%E5%85%A5C++%E6%A8%A1%E6%9D%BF_%E8%A7%A3%E6%9E%84%E6%A8%A1%E6%9D%BF%E6%A8%A1%E6%9D%BF%E5%8F%82%E6%95%B0</id><content type="html" xml:base="https://mikami-w.github.io/cpp/2025/11/07/%E6%B7%B1%E5%85%A5C++%E6%A8%A1%E6%9D%BF_%E8%A7%A3%E6%9E%84%E6%A8%A1%E6%9D%BF%E6%A8%A1%E6%9D%BF%E5%8F%82%E6%95%B0.html"><![CDATA[<p>在 C++ 模板编程的领域中, 我们通常熟悉的是“类型参数” (<code class="language-plaintext highlighter-rouge">typename T</code>) 和“非类型参数” (<code class="language-plaintext highlighter-rouge">int N</code>). 然而, C++ 还提供了一个更高级, 更强大的元编程工具: <strong>模板模板参数 (Template Template Parameters, TTP)</strong>.</p>

<p>正如其名, TTP 允许我们<strong>将“模板”本身作为参数传递给另一个模板</strong>. 这听起来可能有些抽象, 但它是实现高级抽象和“策略基设计 (Policy-Based Design)”的核心机制.</p>

<p>本文将详细探讨模板模板参数的定义, 用途, 语法, 并通过实例分析其在现代 C++ 库 (如 <code class="language-plaintext highlighter-rouge">nlohmann/json</code>) 中的关键作用.</p>

<hr />

<h3 id="1-什么是模板模板参数">1. 什么是模板模板参数?</h3>

<p>要理解 TTP, 我们首先要将其与最常见的“类型参数”进行对比.</p>

<ul>
  <li><strong>类型参数 (<code class="language-plaintext highlighter-rouge">typename T</code>)</strong>
    <ul>
      <li><strong>含义:</strong>  “请给我一个<strong>类型</strong>”.</li>
      <li><strong>传递:</strong>  你传递一个具体的类型, 如 <code class="language-plaintext highlighter-rouge">int</code>, <code class="language-plaintext highlighter-rouge">std::string</code> 或 <code class="language-plaintext highlighter-rouge">MyClass</code>.</li>
      <li><strong>示例:</strong>  <code class="language-plaintext highlighter-rouge">std::vector&lt;int&gt;</code>, 这里 <code class="language-plaintext highlighter-rouge">int</code> 是 <code class="language-plaintext highlighter-rouge">T</code>.</li>
    </ul>
  </li>
  <li><strong>模板模板参数 (<code class="language-plaintext highlighter-rouge">template&lt;...&gt; class C</code>)</strong>
    <ul>
      <li><strong>含义:</strong>  “请给我一个<strong>模板</strong>”.</li>
      <li><strong>传递:</strong>  你传递一个模板本身, 如 <code class="language-plaintext highlighter-rouge">std::vector</code>, <code class="language-plaintext highlighter-rouge">std::list</code> 或 <code class="language-plaintext highlighter-rouge">std::map</code>.</li>
      <li><strong>示例:</strong>  <code class="language-plaintext highlighter-rouge">MyContainer&lt;int, std::vector&gt;</code>, 这里 <code class="language-plaintext highlighter-rouge">std::vector</code> 是 <code class="language-plaintext highlighter-rouge">C</code>.</li>
    </ul>
  </li>
</ul>

<blockquote>
  <p><strong>核心类比:</strong>  如果 <code class="language-plaintext highlighter-rouge">typename T</code> 像是函数的一个<strong>值参数</strong> (<code class="language-plaintext highlighter-rouge">void func(int x)</code>), 你传递的是一个具体的值 (如 <code class="language-plaintext highlighter-rouge">5</code>) ； 那么 TTP 就像是一个<strong>高阶函数参数</strong> (<code class="language-plaintext highlighter-rouge">void high_func(void (*f)(int))</code>), 你传递的是一个函数 (或“行为”) 本身.</p>
</blockquote>

<hr />

<h3 id="2-为什么需要-ttp-核心用途-策略基设计">2. 为什么需要 TTP? 核心用途: 策略基设计</h3>

<p>TTP 的主要目的不是为了处理“什么”数据 (<code class="language-plaintext highlighter-rouge">typename T</code> 已经做到了) , 而是为了定义<strong>“如何”</strong>组织和管理数据. 其最重要和最广泛的应用场景是<strong>策略基设计 (Policy-Based Design)</strong>.</p>

<p>想象一下, 你正在设计一个类, 这个类内部需要一个容器来存储数据.</p>

<p><strong>没有 TTP 的设计 (硬编码) :</strong></p>

<div class="language-cpp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">template</span><span class="o">&lt;</span><span class="k">typename</span> <span class="nc">T</span><span class="p">&gt;</span>
<span class="k">class</span> <span class="nc">DataManager</span> <span class="p">{</span>
<span class="nl">private:</span>
    <span class="n">std</span><span class="o">::</span><span class="n">vector</span><span class="o">&lt;</span><span class="n">T</span><span class="o">&gt;</span> <span class="n">m_data</span><span class="p">;</span> <span class="c1">// 容器类型被写死</span>
<span class="p">};</span>
</code></pre></div></div>

<p>这个设计的<strong>问题</strong>在于其缺乏灵活性. 如果用户在特定场景下发现 <code class="language-plaintext highlighter-rouge">std::list</code> 或 <code class="language-plaintext highlighter-rouge">std::deque</code> 的性能远超 <code class="language-plaintext highlighter-rouge">std::vector</code>, 他们无法更改 <code class="language-plaintext highlighter-rouge">DataManager</code> 的内部实现.</p>

<p><strong>使用 TTP 的设计 (策略注入) :</strong></p>

<div class="language-cpp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// 我们声明 TTP, 并指定它的“签名”</span>
<span class="c1">// 这个签名要求 'Container' 模板至少能接受一个类型参数</span>
<span class="k">template</span><span class="o">&lt;</span>
    <span class="k">typename</span> <span class="nc">T</span><span class="p">,</span>
    <span class="k">template</span><span class="o">&lt;</span><span class="k">typename</span> <span class="nc">Element</span><span class="p">,</span> <span class="k">typename</span><span class="o">...</span><span class="p">&gt;</span> <span class="k">class</span> <span class="nc">Container</span>
<span class="p">&gt;</span>
<span class="k">class</span> <span class="nc">FlexibleDataManager</span> <span class="p">{</span>
<span class="nl">private:</span>
    <span class="c1">// 我们使用 TTP 来“构造”我们的成员变量</span>
    <span class="n">Container</span><span class="o">&lt;</span><span class="n">T</span><span class="o">&gt;</span> <span class="n">m_data</span><span class="p">;</span>
<span class="p">};</span>

<span class="c1">// --- 用户的使用 ---</span>

<span class="c1">// 1. 使用 std::vector 策略</span>
<span class="n">FlexibleDataManager</span><span class="o">&lt;</span><span class="kt">int</span><span class="p">,</span> <span class="n">std</span><span class="o">::</span><span class="n">vector</span><span class="o">&gt;</span> <span class="n">manager_vec</span><span class="p">;</span>

<span class="c1">// 2. 使用 std::list 策略</span>
<span class="n">FlexibleDataManager</span><span class="o">&lt;</span><span class="kt">int</span><span class="p">,</span> <span class="n">std</span><span class="o">::</span><span class="n">list</span><span class="o">&gt;</span> <span class="n">manager_list</span><span class="p">;</span>
</code></pre></div></div>

<p>通过 TTP, 我们允许用户在<strong>编译时</strong>“注入”他们想要的容器策略, 从而在不修改 <code class="language-plaintext highlighter-rouge">FlexibleDataManager</code> 源码的情况下, 完全改变其内部行为和性能特征.</p>

<hr />

<h3 id="3-语法与使用详解">3. 语法与使用详解</h3>

<p>TTP 的语法是它最令人困惑的部分, 但其本质是<strong>描述签名</strong>.</p>

<h4 id="31-声明-declaration">3.1 声明 (Declaration)</h4>

<div class="language-cpp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">template</span> <span class="o">&lt;</span>
    <span class="c1">// 模板模板参数 (TTP)</span>
    <span class="k">template</span><span class="o">&lt;</span><span class="k">typename</span> <span class="nc">U</span><span class="p">&gt;</span> <span class="k">class</span> <span class="nc">Container</span><span class="p">,</span>

    <span class="c1">// 常规类型参数</span>
    <span class="k">typename</span> <span class="nc">T</span>
<span class="p">&gt;</span>
<span class="k">class</span> <span class="nc">MyClass</span> <span class="p">{</span>
    <span class="c1">// ...</span>
    <span class="c1">// 使用 TTP 来实例化一个成员</span>
    <span class="n">Container</span><span class="o">&lt;</span><span class="n">T</span><span class="o">&gt;</span> <span class="n">m_member</span><span class="p">;</span>
<span class="p">};</span>
</code></pre></div></div>

<ul>
  <li><code class="language-plaintext highlighter-rouge">template&lt;typename U&gt; class Container</code>: 这是 TTP 的完整声明.
    <ul>
      <li><code class="language-plaintext highlighter-rouge">template&lt;typename U&gt;</code>: 这部分被称为 TTP 的<strong>签名</strong>. 它声明了 <code class="language-plaintext highlighter-rouge">Container</code> 是一个模板, 并且这个模板期望接受<strong>一个</strong>类型参数 (我们在这里叫它 <code class="language-plaintext highlighter-rouge">U</code>, 名字不重要) .</li>
      <li><code class="language-plaintext highlighter-rouge">class Container</code>: 这是 TTP 的<strong>参数名</strong>, 就像 <code class="language-plaintext highlighter-rouge">T</code> 一样.</li>
    </ul>
  </li>
</ul>

<h4 id="32-实例化-instantiation">3.2 实例化 (Instantiation)</h4>

<p>实例化时, 你必须传递一个<strong>模板名</strong>, 而不是一个完整的类型:</p>

<div class="language-cpp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// 正确: 传递模板名 'std::vector'</span>
<span class="n">MyClass</span><span class="o">&lt;</span><span class="n">std</span><span class="o">::</span><span class="n">vector</span><span class="p">,</span> <span class="kt">int</span><span class="o">&gt;</span> <span class="n">good</span><span class="p">;</span>

<span class="c1">// 错误: 传递了完整的类型 'std::vector&lt;int&gt;'</span>
<span class="c1">// MyClass&lt;std::vector&lt;int&gt;, int&gt; bad; // 编译失败</span>
</code></pre></div></div>

<p>编译器在 <code class="language-plaintext highlighter-rouge">good</code> 的实例化中看到 <code class="language-plaintext highlighter-rouge">std::vector</code>, 它会检查 <code class="language-plaintext highlighter-rouge">std::vector</code> 的声明是否与 <code class="language-plaintext highlighter-rouge">template&lt;typename U&gt;</code> 的签名匹配.</p>

<h4 id="33-关键-签名匹配-signature-matching">3.3 关键: 签名匹配 (Signature Matching)</h4>

<p>TTP 的核心规则是: <strong>你传入的模板, 必须能匹配你声明的 TTP 签名.</strong></p>

<p><strong>示例 1: 简单匹配</strong></p>

<ul>
  <li><strong>TTP 声明:</strong>  <code class="language-plaintext highlighter-rouge">template&lt;typename U&gt; class C</code></li>
  <li><strong><code class="language-plaintext highlighter-rouge">std::vector</code> 声明 (简化):</strong>  <code class="language-plaintext highlighter-rouge">template&lt;typename T, typename Alloc = std::allocator&lt;T&gt;&gt; class vector</code></li>
  <li><strong>匹配结果:</strong>  成功.</li>
  <li><strong>原因:</strong>  <code class="language-plaintext highlighter-rouge">std::vector</code> 至少需要一个模板参数 (<code class="language-plaintext highlighter-rouge">T</code>) , 这与 TTP 签名的 <code class="language-plaintext highlighter-rouge">U</code> 对应. 后续的 <code class="language-plaintext highlighter-rouge">Allocator</code> 参数因为有默认值, 所以是可选的, 编译器可以成功匹配.</li>
</ul>

<p><strong>示例 2: 可变参数匹配 (C++11 及以后)</strong></p>

<p>在实践中, 我们希望 TTP 更加灵活, 能接受像 <code class="language-plaintext highlighter-rouge">std::map</code> 这样有多个参数的模板. 这时, <code class="language-plaintext highlighter-rouge">typename...</code> (可变参数模板) 就派上用场了.</p>

<div class="language-cpp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// 声明一个 Storage 类, 它接受一个TTP, 该TTP</span>
<span class="c1">// 1. 至少要有一个类型参数 (Element)</span>
<span class="c1">// 2. 可以有任意多个后续参数 (Args...), 比如分配器</span>
<span class="k">template</span> <span class="o">&lt;</span>
    <span class="k">typename</span> <span class="nc">T</span><span class="p">,</span>
    <span class="k">template</span><span class="o">&lt;</span><span class="k">typename</span> <span class="nc">Element</span><span class="p">,</span> <span class="k">typename</span><span class="o">...</span> <span class="nc">Args</span><span class="p">&gt;</span> <span class="k">class</span> <span class="nc">Container</span>
<span class="p">&gt;</span>
<span class="k">class</span> <span class="nc">Storage</span> <span class="p">{</span>
<span class="nl">public:</span>
    <span class="kt">void</span> <span class="n">add</span><span class="p">(</span><span class="k">const</span> <span class="n">T</span><span class="o">&amp;</span> <span class="n">item</span><span class="p">)</span> <span class="p">{</span>
        <span class="n">data</span><span class="p">.</span><span class="n">push_back</span><span class="p">(</span><span class="n">item</span><span class="p">);</span>
    <span class="p">}</span>
<span class="nl">private:</span>
    <span class="c1">// 'Container' 被实例化为 'Container&lt;T&gt;'</span>
    <span class="c1">// (假设分配器等后续参数都有默认值)</span>
    <span class="n">Container</span><span class="o">&lt;</span><span class="n">T</span><span class="o">&gt;</span> <span class="n">data</span><span class="p">;</span>
<span class="p">};</span>

<span class="c1">// --- 使用 ---</span>
<span class="n">Storage</span><span class="o">&lt;</span><span class="kt">int</span><span class="p">,</span> <span class="n">std</span><span class="o">::</span><span class="n">vector</span><span class="o">&gt;</span> <span class="n">vec_storage</span><span class="p">;</span> <span class="c1">// 匹配！</span>
<span class="n">Storage</span><span class="o">&lt;</span><span class="kt">int</span><span class="p">,</span> <span class="n">std</span><span class="o">::</span><span class="n">list</span><span class="o">&gt;</span>   <span class="n">list_storage</span><span class="p">;</span> <span class="c1">// 匹配！</span>
<span class="n">Storage</span><span class="o">&lt;</span><span class="kt">int</span><span class="p">,</span> <span class="n">std</span><span class="o">::</span><span class="n">deque</span><span class="o">&gt;</span>  <span class="n">deque_storage</span><span class="p">;</span> <span class="c1">// 匹配！</span>
</code></pre></div></div>

<p><code class="language-plaintext highlighter-rouge">template&lt;typename Element, typename... Args&gt; class Container</code> 是现代 C++ 中 TTP 最常用, 最灵活的“签名”形式.</p>

<hr />

<h3 id="4-案例研究-nlohmannjson-的-basic_json">4. 案例研究: <code class="language-plaintext highlighter-rouge">nlohmann/json</code> 的 <code class="language-plaintext highlighter-rouge">basic_json</code></h3>

<p><code class="language-plaintext highlighter-rouge">nlohmann/json</code> 库中的 <code class="language-plaintext highlighter-rouge">basic_json</code> 是 TTP 策略基设计的绝佳范例.</p>

<div class="language-cpp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">template</span><span class="o">&lt;</span>
    <span class="k">template</span><span class="o">&lt;</span><span class="k">typename</span> <span class="nc">U</span><span class="p">,</span> <span class="k">typename</span> <span class="nc">V</span><span class="p">,</span> <span class="k">typename</span><span class="o">...</span> <span class="nc">Args</span><span class="p">&gt;</span> <span class="k">class</span> <span class="nc">ObjectType</span> <span class="o">=</span> <span class="n">std</span><span class="o">::</span><span class="n">map</span><span class="p">,</span>
    <span class="k">template</span><span class="o">&lt;</span><span class="k">typename</span> <span class="nc">U</span><span class="p">,</span> <span class="k">typename</span><span class="o">...</span> <span class="nc">Args</span><span class="p">&gt;</span> <span class="k">class</span> <span class="nc">ArrayType</span> <span class="o">=</span> <span class="n">std</span><span class="o">::</span><span class="n">vector</span><span class="p">,</span>
    <span class="k">class</span> <span class="nc">StringType</span> <span class="o">=</span> <span class="n">std</span><span class="o">::</span><span class="n">string</span><span class="p">,</span>
    <span class="k">class</span> <span class="nc">NumberIntegerType</span> <span class="o">=</span> <span class="n">std</span><span class="o">::</span><span class="kt">int64_t</span><span class="p">,</span>
    <span class="c1">// ... 其他类型 ...</span>
    <span class="k">template</span><span class="o">&lt;</span><span class="k">typename</span> <span class="nc">U</span><span class="p">&gt;</span> <span class="k">class</span> <span class="nc">AllocatorType</span> <span class="o">=</span> <span class="n">std</span><span class="o">::</span><span class="n">allocator</span>
<span class="p">&gt;</span>
<span class="k">class</span> <span class="nc">basic_json</span><span class="p">;</span>
</code></pre></div></div>

<p>让我们分析其中两个 TTP:</p>

<ol>
  <li><strong><code class="language-plaintext highlighter-rouge">ObjectType = std::map</code></strong>
    <ul>
      <li><strong>TTP 声明:</strong>  <code class="language-plaintext highlighter-rouge">template&lt;typename U, typename V, typename... Args&gt; class ObjectType</code></li>
      <li><strong>签名要求:</strong>  传入的模板必须能接受至少两个类型参数 (<code class="language-plaintext highlighter-rouge">U</code> 键类型, <code class="language-plaintext highlighter-rouge">V</code> 值类型) 以及任意多个后续参数 (<code class="language-plaintext highlighter-rouge">...Args</code>).</li>
      <li><strong>默认值:</strong>  <code class="language-plaintext highlighter-rouge">std::map</code> (其声明为 <code class="language-plaintext highlighter-rouge">template&lt;Key, T, Compare, Alloc&gt;</code>, 完美匹配) .</li>
      <li><strong>灵活性:</strong>  用户可以轻松地将此模板参数替换为 <code class="language-plaintext highlighter-rouge">std::unordered_map</code> (其声明为 <code class="language-plaintext highlighter-rouge">template&lt;Key, T, Hash, Pred, Alloc&gt;</code>, 同样完美匹配) , 从而将 JSON 对象的内部存储从<strong>红黑树</strong>切换为<strong>哈希表</strong>, 以获取不同的性能权衡.</li>
    </ul>
  </li>
  <li><strong><code class="language-plaintext highlighter-rouge">ArrayType = std::vector</code></strong>
    <ul>
      <li><strong>TTP 声明:</strong>  <code class="language-plaintext highlighter-rouge">template&lt;typename U, typename... Args&gt; class ArrayType</code></li>
      <li><strong>签名要求:</strong>  传入的模板必须能接受至少一个类型参数 (<code class="language-plaintext highlighter-rouge">U</code> 元素类型) 和任意多个后续参数.</li>
      <li><strong>默认值:</strong>  <code class="language-plaintext highlighter-rouge">std::vector</code>.</li>
      <li><strong>灵活性:</strong>  用户可以将其替换为 <code class="language-plaintext highlighter-rouge">std::deque</code>.</li>
    </ul>
  </li>
</ol>

<p>我们通常使用的 <code class="language-plaintext highlighter-rouge">nlohmann::json</code>, 只不过是 <code class="language-plaintext highlighter-rouge">basic_json</code> 使用所有默认策略 (<code class="language-plaintext highlighter-rouge">std::map</code>, <code class="language-plaintext highlighter-rouge">std::vector</code>, <code class="language-plaintext highlighter-rouge">std::string</code> 等) 的一个类型别名 (type alias) 罢了.</p>

<hr />

<h3 id="总结">总结</h3>

<p>模板模板参数 (TTP) 是 C++ 元编程的一个高级工具. 它将 C++ 模板的抽象能力从“对类型进行参数化”提升到了“对模板 (即策略) 进行参数化”.</p>

<p>虽然其语法初看较为复杂, 但其核心价值在于实现了<strong>策略基设计</strong>, 允许库的作者设计出高度灵活, 可配置的组件, 同时将这些复杂性对默认用户隐藏起来. 理解 TTP 是深入理解现代 C++ 库设计 (如 <code class="language-plaintext highlighter-rouge">nlohmann/json</code>) 和高级模板编程的关键一步.</p>]]></content><author><name>Mikami</name></author><category term="cpp" /><category term="cpp" /><category term="code" /><summary type="html"><![CDATA[在 C++ 模板编程的领域中, 我们通常熟悉的是“类型参数” (typename T) 和“非类型参数” (int N). 然而, C++ 还提供了一个更高级, 更强大的元编程工具: 模板模板参数 (Template Template Parameters, TTP).]]></summary></entry><entry><title type="html">以 std::enable_if 为切入点：初识 C++ 模板元编程与 SFINAE</title><link href="https://mikami-w.github.io/cpp/2025/10/24/%E4%BB%A5std_enable_if%E4%B8%BA%E5%88%87%E5%85%A5%E7%82%B9_%E5%88%9D%E8%AF%86C++%E6%A8%A1%E6%9D%BF%E5%85%83%E7%BC%96%E7%A8%8B%E4%B8%8ESFINAE.html" rel="alternate" type="text/html" title="以 std::enable_if 为切入点：初识 C++ 模板元编程与 SFINAE" /><published>2025-10-24T00:00:00+00:00</published><updated>2025-10-24T00:00:00+00:00</updated><id>https://mikami-w.github.io/cpp/2025/10/24/%E4%BB%A5std_enable_if%E4%B8%BA%E5%88%87%E5%85%A5%E7%82%B9_%E5%88%9D%E8%AF%86C++%E6%A8%A1%E6%9D%BF%E5%85%83%E7%BC%96%E7%A8%8B%E4%B8%8ESFINAE</id><content type="html" xml:base="https://mikami-w.github.io/cpp/2025/10/24/%E4%BB%A5std_enable_if%E4%B8%BA%E5%88%87%E5%85%A5%E7%82%B9_%E5%88%9D%E8%AF%86C++%E6%A8%A1%E6%9D%BF%E5%85%83%E7%BC%96%E7%A8%8B%E4%B8%8ESFINAE.html"><![CDATA[<p><strong><em>本文完整示例代码来源于 <a href="https://en.cppreference.com/w/cpp/types/enable_if.html">cppreference.com</a> (example)</em></strong></p>

<hr />

<p>在 C++ 模板编程的领域中, 我们经常需要根据类型的不同<em>属性</em> (Traits) 来选择性地启用或禁用特定的函数重载或类模板实现. 例如, 我们可能希望一个 <code class="language-plaintext highlighter-rouge">destroy</code> 函数对平凡可析构的类型 (如 <code class="language-plaintext highlighter-rouge">int</code>) 执行空操作, 而对非平凡可析构的类型 (如 <code class="language-plaintext highlighter-rouge">std::string</code>) 显式调用其析构函数.</p>

<p>实现这种编译期条件分支的核心机制之一, 就是 SFINAE, 而 <code class="language-plaintext highlighter-rouge">std::enable_if</code> 则是利用这一机制的标准库工具. 本文将从 <code class="language-plaintext highlighter-rouge">std::enable_if</code> 出发, 详细解析其工作原理及 SFINAE 机制, 并探讨其在不同场景下的应用.</p>

<h2 id="核心机制-sfinae-替换失败并非错误">核心机制: SFINAE (替换失败并非错误)</h2>

<p>SFINAE 的全称是 <strong>“Substitution Failure Is Not An Error”</strong>, 即“<strong>替换失败并非错误</strong>”.</p>

<p>这是 C++ 模板重载解析的一条核心规则. 其含义是: 当编译器在为模板函数 (或类模板) 进行参数推导和替换时, 如果某个模板的签名 (包括返回类型, 参数类型等) 在替换过程中因为无效的类型操作而导致“失败” (例如, 试图访问一个不存在的成员类型 <code class="language-plaintext highlighter-rouge">T::type</code>) , 编译器<strong>不会立即报错</strong>.</p>

<p>相反, 编译器会<strong>默默地将这个导致替换失败的模板从重载候选集中移除</strong>, 然后继续尝试匹配其他候选函数. 如果最终只剩下一个有效的候选, 编译成功；如果剩下零个或多个, 编译器才会报告“找不到匹配函数”或“重载歧义”的错误.</p>

<p>SFINAE 是 C++ 模板元编程实现编译期自省 (Introspection) 和条件分派 (Dispatch) 的基石.</p>

<h2 id="核心工具-stdenable_if">核心工具: std::enable_if</h2>

<p><code class="language-plaintext highlighter-rouge">std::enable_if</code> 是一个专门用于“故意”触发 SFINAE 的模板结构体, 其定义 (C++11) 大致如下:</p>

<div class="language-cpp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// C++11, 位于 &lt;type_traits&gt;</span>
<span class="c1">// 基础模板</span>
<span class="k">template</span><span class="o">&lt;</span><span class="kt">bool</span> <span class="n">Condition</span><span class="p">,</span> <span class="k">class</span> <span class="nc">T</span> <span class="o">=</span> <span class="kt">void</span><span class="p">&gt;</span>
<span class="k">struct</span> <span class="nc">enable_if</span> <span class="p">{};</span>

<span class="c1">// 当 Condition 为 true 时的特化版本</span>
<span class="k">template</span><span class="o">&lt;</span><span class="k">class</span> <span class="nc">T</span><span class="p">&gt;</span>
<span class="k">struct</span> <span class="nc">enable_if</span><span class="o">&lt;</span><span class="nb">true</span><span class="p">,</span> <span class="n">T</span><span class="o">&gt;</span> <span class="p">{</span>
  <span class="k">using</span> <span class="n">type</span> <span class="o">=</span> <span class="n">T</span><span class="p">;</span> <span class="c1">// 定义了一个名为 'type' 的成员类型</span>
<span class="p">};</span>
</code></pre></div></div>

<p>其行为非常简单:</p>

<ul>
  <li><strong>当 <code class="language-plaintext highlighter-rouge">Condition</code> 为 <code class="language-plaintext highlighter-rouge">true</code> 时</strong>: <code class="language-plaintext highlighter-rouge">std::enable_if&lt;true, T&gt;</code> 会有一个公开的成员类型 <code class="language-plaintext highlighter-rouge">type</code> (默认为 <code class="language-plaintext highlighter-rouge">void</code>) .</li>
  <li><strong>当 <code class="language-plaintext highlighter-rouge">Condition</code> 为 <code class="language-plaintext highlighter-rouge">false</code> 时</strong>: <code class="language-plaintext highlighter-rouge">std::enable_if&lt;false, T&gt;</code> 匹配的是基础模板, 该模板<strong>内部没有任何成员定义</strong>.</li>
</ul>

<p>在 C++14 中, 我们有了一个更方便的别名助手 <code class="language-plaintext highlighter-rouge">std::enable_if_t</code>:</p>

<div class="language-cpp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">template</span><span class="o">&lt;</span><span class="kt">bool</span> <span class="n">Condition</span><span class="p">,</span> <span class="k">class</span> <span class="nc">T</span> <span class="o">=</span> <span class="kt">void</span><span class="p">&gt;</span>
<span class="k">using</span> <span class="n">enable_if_t</span> <span class="o">=</span> <span class="k">typename</span> <span class="n">std</span><span class="o">::</span><span class="n">enable_if</span><span class="o">&lt;</span><span class="n">Condition</span><span class="p">,</span> <span class="n">T</span><span class="o">&gt;::</span><span class="n">type</span><span class="p">;</span>
</code></pre></div></div>

<p><strong>关键技巧</strong>: 我们将 <code class="language-plaintext highlighter-rouge">std::enable_if_t&lt;Condition&gt;</code> (或 <code class="language-plaintext highlighter-rouge">typename std::enable_if&lt;Condition&gt;::type</code>) 放置在模板签名中.</p>

<ul>
  <li>如果 <code class="language-plaintext highlighter-rouge">Condition</code> 为 <code class="language-plaintext highlighter-rouge">true</code>, <code class="language-plaintext highlighter-rouge">enable_if_t</code> 会成功解析为一个类型 (如 <code class="language-plaintext highlighter-rouge">void</code>) , 函数签名有效.</li>
  <li>如果 <code class="language-plaintext highlighter-rouge">Condition</code> 为 <code class="language-plaintext highlighter-rouge">false</code>, <code class="language-plaintext highlighter-rouge">enable_if_t</code> 会尝试访问一个<strong>不存在</strong>的 <code class="language-plaintext highlighter-rouge">::type</code>, 导致模板<strong>替换失败</strong>. 此时 SFINAE 机制启动, 该模板被安全地从候选集中移除.</li>
</ul>

<hr />

<h2 id="stdenable_if-的应用模式分析">std::enable_if 的应用模式分析</h2>

<p><code class="language-plaintext highlighter-rouge">std::enable_if</code> 可以被巧妙地放置在函数签名的多个位置, 以达到 SFINAE 的效果.</p>

<h3 id="模式一-通过返回类型启用">模式一: 通过返回类型启用</h3>

<p>这是最经典的应用方式. <code class="language-plaintext highlighter-rouge">std::enable_if</code> 的结果构成了函数返回类型的一部分.</p>

<div class="language-cpp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="cp">#include</span> <span class="cpf">&lt;iostream&gt;</span><span class="cp">
#include</span> <span class="cpf">&lt;string&gt;</span><span class="cp">
#include</span> <span class="cpf">&lt;type_traits&gt;</span><span class="cp">
</span>
<span class="c1">// #1: 仅当 T 是平凡默认构造时启用</span>
<span class="k">template</span><span class="o">&lt;</span><span class="k">class</span> <span class="nc">T</span><span class="p">&gt;</span>
<span class="k">typename</span> <span class="n">std</span><span class="o">::</span><span class="n">enable_if</span><span class="o">&lt;</span><span class="n">std</span><span class="o">::</span><span class="n">is_trivially_default_constructible</span><span class="o">&lt;</span><span class="n">T</span><span class="o">&gt;::</span><span class="n">value</span><span class="o">&gt;::</span><span class="n">type</span> 
<span class="nf">construct</span><span class="p">(</span><span class="n">T</span><span class="o">*</span><span class="p">)</span> <span class="p">{</span>
    <span class="n">std</span><span class="o">::</span><span class="n">cout</span> <span class="o">&lt;&lt;</span> <span class="s">"default constructing trivially default constructible T</span><span class="se">\n</span><span class="s">"</span><span class="p">;</span>
<span class="p">}</span>

<span class="c1">// #2: 仅当 T 不是平凡默认构造时启用</span>
<span class="k">template</span><span class="o">&lt;</span><span class="k">class</span> <span class="nc">T</span><span class="p">&gt;</span>
<span class="k">typename</span> <span class="n">std</span><span class="o">::</span><span class="n">enable_if</span><span class="o">&lt;!</span><span class="n">std</span><span class="o">::</span><span class="n">is_trivially_default_constructible</span><span class="o">&lt;</span><span class="n">T</span><span class="o">&gt;::</span><span class="n">value</span><span class="o">&gt;::</span><span class="n">type</span> 
<span class="n">construct</span><span class="p">(</span><span class="n">T</span><span class="o">*</span> <span class="n">p</span><span class="p">)</span> <span class="p">{</span>
    <span class="n">std</span><span class="o">::</span><span class="n">cout</span> <span class="o">&lt;&lt;</span> <span class="s">"default constructing non-trivially default constructible T</span><span class="se">\n</span><span class="s">"</span><span class="p">;</span>
    <span class="o">::</span><span class="k">new</span><span class="p">(</span><span class="n">p</span><span class="p">)</span> <span class="n">T</span><span class="p">;</span> <span class="c1">// 使用 placement new</span>
<span class="p">}</span>
</code></pre></div></div>

<p>解析:</p>

<p>当我们调用 construct(some_ptr) 时:</p>

<ol>
  <li><strong>若 <code class="language-plaintext highlighter-rouge">T</code> 为 <code class="language-plaintext highlighter-rouge">int</code></strong>:
    <ul>
      <li><code class="language-plaintext highlighter-rouge">std::is_trivially_default_constructible&lt;int&gt;::value</code> 为 <code class="language-plaintext highlighter-rouge">true</code>.</li>
      <li>重载 #1: <code class="language-plaintext highlighter-rouge">std::enable_if&lt;true&gt;::type</code> 变为 <code class="language-plaintext highlighter-rouge">void</code>. 函数签名为 <code class="language-plaintext highlighter-rouge">void construct(int*)</code>. <strong>候选有效</strong>.</li>
      <li>重载 #2: <code class="language-plaintext highlighter-rouge">!std::enable_if&lt;true&gt;::type</code> (即 <code class="language-plaintext highlighter-rouge">false</code>). 尝试访问 <code class="language-plaintext highlighter-rouge">std::enable_if&lt;false&gt;::type</code> 失败. <strong>SFINAE 触发</strong>, 此重载被忽略.</li>
      <li>结果: 调用 #1.</li>
    </ul>
  </li>
  <li><strong>若 <code class="language-plaintext highlighter-rouge">T</code> 为 <code class="language-plaintext highlighter-rouge">std::string</code></strong>:
    <ul>
      <li><code class="language-plaintext highlighter-rouge">std::is_trivially_default_constructible&lt;std::string&gt;::value</code> 为 <code class="language-plaintext highlighter-rouge">false</code>.</li>
      <li>重载 #1: <code class="language-plaintext highlighter-rouge">std::enable_if&lt;false&gt;::type</code> 失败. <strong>SFINAE 触发</strong>, 此重载被忽略.</li>
      <li>重载 #2: <code class="language-plaintext highlighter-rouge">!std::enable_if&lt;false&gt;::type</code> (即 <code class="language-plaintext highlighter-rouge">true</code>). 函数签名为 <code class="language-plaintext highlighter-rouge">void construct(std::string*)</code>. <strong>候选有效</strong>.</li>
      <li>结果: 调用 #2.</li>
    </ul>
  </li>
</ol>

<p>使用 <code class="language-plaintext highlighter-rouge">std::enable_if_t</code> (C++14) 可以使语法更简洁:</p>

<div class="language-cpp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// 仅当 T 可以用 Args... 构造时启用</span>
<span class="k">template</span><span class="o">&lt;</span><span class="k">class</span> <span class="nc">T</span><span class="p">,</span> <span class="k">class</span><span class="o">...</span> <span class="nc">Args</span><span class="p">&gt;</span>
<span class="n">std</span><span class="o">::</span><span class="n">enable_if_t</span><span class="o">&lt;</span><span class="n">std</span><span class="o">::</span><span class="n">is_constructible</span><span class="o">&lt;</span><span class="n">T</span><span class="p">,</span> <span class="n">Args</span><span class="o">&amp;&amp;</span><span class="p">...</span><span class="o">&gt;::</span><span class="n">value</span><span class="o">&gt;</span>
<span class="n">construct</span><span class="p">(</span><span class="n">T</span><span class="o">*</span> <span class="n">p</span><span class="p">,</span> <span class="n">Args</span><span class="o">&amp;&amp;</span><span class="p">...</span> <span class="n">args</span><span class="p">)</span> <span class="p">{</span>
    <span class="n">std</span><span class="o">::</span><span class="n">cout</span> <span class="o">&lt;&lt;</span> <span class="s">"constructing T with operation</span><span class="se">\n</span><span class="s">"</span><span class="p">;</span>
    <span class="o">::</span><span class="k">new</span><span class="p">(</span><span class="n">p</span><span class="p">)</span> <span class="n">T</span><span class="p">(</span><span class="k">static_cast</span><span class="o">&lt;</span><span class="n">Args</span><span class="o">&amp;&amp;&gt;</span><span class="p">(</span><span class="n">args</span><span class="p">)...);</span>
<span class="p">}</span>
</code></pre></div></div>

<h3 id="模式二-通过函数参数启用">模式二: 通过函数参数启用</h3>

<p>SFINAE 也可以在函数参数类型中触发.</p>

<div class="language-cpp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">template</span><span class="o">&lt;</span><span class="k">class</span> <span class="nc">T</span><span class="p">&gt;</span>
<span class="kt">void</span> <span class="nf">destroy</span><span class="p">(</span>
    <span class="n">T</span><span class="o">*</span><span class="p">,</span> 
    <span class="k">typename</span> <span class="n">std</span><span class="o">::</span><span class="n">enable_if</span><span class="o">&lt;</span>
        <span class="n">std</span><span class="o">::</span><span class="n">is_trivially_destructible</span><span class="o">&lt;</span><span class="n">T</span><span class="o">&gt;::</span><span class="n">value</span>
    <span class="o">&gt;::</span><span class="n">type</span><span class="o">*</span> <span class="o">=</span> <span class="mi">0</span><span class="p">)</span>
<span class="p">{</span>
    <span class="n">std</span><span class="o">::</span><span class="n">cout</span> <span class="o">&lt;&lt;</span> <span class="s">"destroying trivially destructible T</span><span class="se">\n</span><span class="s">"</span><span class="p">;</span>
<span class="p">}</span>
</code></pre></div></div>

<p>解析:</p>

<p>这里的技巧是添加一个额外的, 有默认值 (= 0 即 nullptr) 的函数参数. 该参数的类型依赖于 std::enable_if.</p>

<ol>
  <li><strong>若 <code class="language-plaintext highlighter-rouge">T</code> 为 <code class="language-plaintext highlighter-rouge">int</code></strong>:
    <ul>
      <li><code class="language-plaintext highlighter-rouge">std::is_trivially_destructible&lt;int&gt;</code> 为 <code class="language-plaintext highlighter-rouge">true</code>.</li>
      <li><code class="language-plaintext highlighter-rouge">std::enable_if&lt;true&gt;::type</code> 变为 <code class="language-plaintext highlighter-rouge">void</code>.</li>
      <li>第二个参数的类型解析为 <code class="language-plaintext highlighter-rouge">void*</code>.</li>
      <li>函数签名变为 <code class="language-plaintext highlighter-rouge">void destroy(int*, void* = 0)</code>. <strong>候选有效</strong>.</li>
    </ul>
  </li>
  <li><strong>若 <code class="language-plaintext highlighter-rouge">T</code> 为 <code class="language-plaintext highlighter-rouge">std::string</code></strong>:
    <ul>
      <li><code class="language-plaintext highlighter-rouge">std::is_trivially_destructible&lt;std::string&gt;</code> 为 <code class="language-plaintext highlighter-rouge">false</code>.</li>
      <li><code class="language-plaintext highlighter-rouge">std::enable_if&lt;false&gt;::type</code> 失败. <strong>SFINAE 触发</strong>, 此重载被忽略.</li>
    </ul>
  </li>
</ol>

<p>这种方式在 C++11 之前很流行, 但它会轻微地改变函数的签名 (增加了一个参数) .</p>

<h3 id="模式三-通过模板参数启用">模式三: 通过模板参数启用</h3>

<p>这是一种更现代且更清晰的 SFINAE 技巧, 它不改变函数的参数列表. SFINAE 发生在模板参数列表中.</p>

<p><strong>3a. 依赖非类型模板参数 (Non-type template parameter)</strong></p>

<div class="language-cpp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">template</span><span class="o">&lt;</span><span class="k">class</span> <span class="nc">T</span><span class="p">,</span>
         <span class="k">typename</span> <span class="nc">std</span><span class="o">::</span><span class="n">enable_if</span><span class="o">&lt;</span>
             <span class="o">!</span><span class="n">std</span><span class="o">::</span><span class="n">is_trivially_destructible</span><span class="o">&lt;</span><span class="n">T</span><span class="p">&gt;{}</span> <span class="o">&amp;&amp;</span>
             <span class="p">(</span><span class="n">std</span><span class="o">::</span><span class="n">is_class</span><span class="o">&lt;</span><span class="n">T</span><span class="o">&gt;</span><span class="p">{}</span> <span class="o">||</span> <span class="n">std</span><span class="o">::</span><span class="n">is_union</span><span class="o">&lt;</span><span class="n">T</span><span class="o">&gt;</span><span class="p">{}),</span>
             <span class="kt">bool</span><span class="o">&gt;::</span><span class="n">type</span> <span class="o">=</span> <span class="nb">true</span><span class="o">&gt;</span> <span class="c1">// 默认值为 true 的 bool 类型</span>
<span class="kt">void</span> <span class="n">destroy</span><span class="p">(</span><span class="n">T</span><span class="o">*</span> <span class="n">t</span><span class="p">)</span>
<span class="p">{</span>
    <span class="n">std</span><span class="o">::</span><span class="n">cout</span> <span class="o">&lt;&lt;</span> <span class="s">"destroying non-trivially destructible T</span><span class="se">\n</span><span class="s">"</span><span class="p">;</span>
    <span class="n">t</span><span class="o">-&gt;~</span><span class="n">T</span><span class="p">();</span>
<span class="p">}</span>
</code></pre></div></div>

<p>解析:</p>

<p>我们添加了一个匿名的非类型模板参数, 其类型由 std::enable_if&lt;Condition, bool&gt;::type 决定.</p>

<ol>
  <li><strong>若 <code class="language-plaintext highlighter-rouge">T</code> 为 <code class="language-plaintext highlighter-rouge">std::string</code></strong>:
    <ul>
      <li>条件为 <code class="language-plaintext highlighter-rouge">true</code>.</li>
      <li><code class="language-plaintext highlighter-rouge">std::enable_if&lt;true, bool&gt;::type</code> 解析为 <code class="language-plaintext highlighter-rouge">bool</code>.</li>
      <li>模板签名变为 <code class="language-plaintext highlighter-rouge">template&lt;class T, bool = true&gt; void destroy(T*)</code>. <strong>候选有效</strong>.</li>
    </ul>
  </li>
  <li><strong>若 <code class="language-plaintext highlighter-rouge">T</code> 为 <code class="language-plaintext highlighter-rouge">int</code></strong>:
    <ul>
      <li>条件为 <code class="language-plaintext highlighter-rouge">false</code>.</li>
      <li><code class="language-plaintext highlighter-rouge">std::enable_if&lt;false, bool&gt;::type</code> 失败. <strong>SFINAE 触发</strong>, 此重载被忽略.</li>
    </ul>
  </li>
</ol>

<p><strong>3b. 依赖类型模板参数 (Type template parameter)</strong></p>

<p>这是目前最受推荐的 SFINAE 模式 (在 C++20 Concepts 出现之前) .</p>

<div class="language-cpp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">template</span><span class="o">&lt;</span><span class="k">class</span> <span class="nc">T</span><span class="p">,</span>
	 <span class="k">typename</span> <span class="o">=</span> <span class="n">std</span><span class="o">::</span><span class="n">enable_if_t</span><span class="o">&lt;</span><span class="n">std</span><span class="o">::</span><span class="n">is_array</span><span class="o">&lt;</span><span class="n">T</span><span class="p">&gt;</span><span class="o">::</span><span class="n">value</span><span class="o">&gt;&gt;</span>
<span class="kt">void</span> <span class="nf">destroy</span><span class="p">(</span><span class="n">T</span><span class="o">*</span> <span class="n">t</span><span class="p">)</span> <span class="c1">// 注意: 函数签名是干净的 void(T*)</span>
<span class="p">{</span>
    <span class="c1">// ... 针对数组的实现 ...</span>
    <span class="n">std</span><span class="o">::</span><span class="n">cout</span> <span class="o">&lt;&lt;</span> <span class="s">"destroying array</span><span class="se">\n</span><span class="s">"</span><span class="p">;</span>
<span class="p">}</span>
</code></pre></div></div>

<p>解析:</p>

<p>我们添加了一个匿名的类型模板参数, 其类型默认为 std::enable_if_t 的结果.</p>

<ol>
  <li><strong>若 <code class="language-plaintext highlighter-rouge">T</code> 为 <code class="language-plaintext highlighter-rouge">int[10]</code></strong>:
    <ul>
      <li><code class="language-plaintext highlighter-rouge">std::is_array&lt;int[10]&gt;::value</code> 为 <code class="language-plaintext highlighter-rouge">true</code>.</li>
      <li><code class="language-plaintext highlighter-rouge">std::enable_if_t&lt;true&gt;</code> 解析为 <code class="language-plaintext highlighter-rouge">void</code>.</li>
      <li>模板签名变为 <code class="language-plaintext highlighter-rouge">template&lt;class T, typename = void&gt; void destroy(T*)</code>. <strong>候选有效</strong>.</li>
    </ul>
  </li>
  <li><strong>若 <code class="language-plaintext highlighter-rouge">T</code> 为 <code class="language-plaintext highlighter-rouge">int</code></strong>:
    <ul>
      <li><code class="language-plaintext highlighter-rouge">std::is_array&lt;int&gt;::value</code> 为 <code class="language-plaintext highlighter-rouge">false</code>.</li>
      <li><code class="language-plaintext highlighter-rouge">std::enable_if_t&lt;false&gt;</code> 失败. <strong>SFINAE 触发</strong>, 此重载被忽略.</li>
    </ul>
  </li>
</ol>

<hr />

<h2 id="深入辨析-sfinae-偏特化-vs-显式特化">深入辨析: SFINAE 偏特化 vs. 显式特化</h2>

<p><code class="language-plaintext highlighter-rouge">std::enable_if</code> 不仅用于函数, 还常用于有条件地启用<strong>类模板偏特化</strong>. 这引出了一个重要的问题: 它和显式 (全) 特化有何区别？</p>

<p>我们来对比以下两种写法:</p>

<h3 id="写法-1-enable_if-偏特化-基于规则">写法 1: enable_if 偏特化 (基于规则)</h3>

<div class="language-cpp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// primary template (主模板)</span>
<span class="c1">// 注意: 有两个模板参数</span>
<span class="k">template</span><span class="o">&lt;</span><span class="k">class</span> <span class="nc">T</span><span class="p">,</span> <span class="k">class</span> <span class="nc">Enable</span> <span class="o">=</span> <span class="kt">void</span><span class="p">&gt;</span>
<span class="k">class</span> <span class="nc">A</span> <span class="p">{};</span> 

<span class="c1">// partial specialization (偏特化)</span>
<span class="k">template</span><span class="o">&lt;</span><span class="k">class</span> <span class="nc">T</span><span class="p">&gt;</span>
<span class="k">class</span> <span class="nc">A</span><span class="o">&lt;</span><span class="n">T</span><span class="p">,</span> <span class="k">typename</span> <span class="n">std</span><span class="o">::</span><span class="n">enable_if</span><span class="o">&lt;</span><span class="n">std</span><span class="o">::</span><span class="n">is_floating_point</span><span class="o">&lt;</span><span class="n">T</span><span class="o">&gt;::</span><span class="n">value</span><span class="o">&gt;::</span><span class="n">type</span><span class="o">&gt;</span>
<span class="p">{</span>
    <span class="c1">// ... 这个版本只为浮点数存在</span>
<span class="p">};</span>
</code></pre></div></div>

<ul>
  <li><strong>机制:</strong> 这是<strong>偏特化 (Partial Specialization)</strong>. 我们没有固定 <code class="language-plaintext highlighter-rouge">T</code>, 而是通过 SFINAE 让第二个参数 <code class="language-plaintext highlighter-rouge">Enable</code> 在 <code class="language-plaintext highlighter-rouge">T</code> 是浮点数时解析为 <code class="language-plaintext highlighter-rouge">void</code>, 从而匹配这个特化版本.</li>
  <li><strong>泛化能力:</strong> <strong>高</strong>. 这是一个<strong>基于规则</strong>的实现. 它会自动匹配<strong>所有</strong>符合 <code class="language-plaintext highlighter-rouge">std::is_floating_point</code> 规则的类型 (<code class="language-plaintext highlighter-rouge">float</code>, <code class="language-plaintext highlighter-rouge">double</code>, <code class="language-plaintext highlighter-rouge">long double</code>) .</li>
  <li><strong>解析 <code class="language-plaintext highlighter-rouge">A&lt;double&gt;</code></strong>: <code class="language-plaintext highlighter-rouge">std::enable_if&lt;true&gt;::type</code> 为 <code class="language-plaintext highlighter-rouge">void</code>. 特化版本匹配 <code class="language-plaintext highlighter-rouge">A&lt;double, void&gt;</code>, 优于主模板, 被选中.</li>
  <li><strong>解析 <code class="language-plaintext highlighter-rouge">A&lt;int&gt;</code></strong>: <code class="language-plaintext highlighter-rouge">std::enable_if&lt;false&gt;::type</code> 失败. SFINAE 触发, 特化版本被忽略. 主模板 <code class="language-plaintext highlighter-rouge">A&lt;int, void&gt;</code> 被选中.</li>
</ul>

<h3 id="写法-2-显式特化-基于列表">写法 2: 显式特化 (基于列表)</h3>

<div class="language-cpp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// primary template (主模板)</span>
<span class="c1">// 注意: 只有一个模板参数</span>
<span class="k">template</span><span class="o">&lt;</span><span class="k">class</span> <span class="nc">T</span><span class="p">&gt;</span>
<span class="k">class</span> <span class="nc">A</span> <span class="p">{};</span> 

<span class="c1">// explicit (full) specialization (显式特化)</span>
<span class="k">template</span><span class="o">&lt;</span><span class="p">&gt;</span>
<span class="k">class</span> <span class="nc">A</span><span class="o">&lt;</span><span class="kt">double</span><span class="o">&gt;</span>
<span class="p">{</span>
    <span class="c1">// ... 只为 double 的实现</span>
<span class="p">};</span>

<span class="c1">// 必须为 float 也提供一个</span>
<span class="k">template</span><span class="o">&lt;</span><span class="p">&gt;</span>
<span class="k">class</span> <span class="nc">A</span><span class="o">&lt;</span><span class="kt">float</span><span class="o">&gt;</span>
<span class="p">{</span>
    <span class="c1">// ... 只为 float 的实现</span>
<span class="p">};</span>
</code></pre></div></div>

<ul>
  <li><strong>机制:</strong> 这是<strong>显式特化 (Explicit/Full Specialization)</strong>. <code class="language-plaintext highlighter-rouge">template&lt;&gt;</code> 表明我们为所有模板参数提供了具体类型.</li>
  <li><strong>泛化能力:</strong> <strong>低</strong>. 这是一个<strong>基于列表</strong>的实现. 你必须为你关心的<strong>每一个</strong>具体类型编写一个特化版本.</li>
  <li><strong>解析 <code class="language-plaintext highlighter-rouge">A&lt;double&gt;</code></strong>: 精确匹配 <code class="language-plaintext highlighter-rouge">A&lt;double&gt;</code> 特化版本.</li>
  <li><strong>解析 <code class="language-plaintext highlighter-rouge">A&lt;long double&gt;</code></strong>: <strong>匹配失败</strong>. 编译器找不到 <code class="language-plaintext highlighter-rouge">A&lt;long double&gt;</code> 的显式特化, 因此它会回头使用<strong>主模板</strong> <code class="language-plaintext highlighter-rouge">template&lt;class T&gt;</code>, 实例化 <code class="language-plaintext highlighter-rouge">A&lt;long double&gt;</code>. 这很可能不是我们期望的行为.</li>
</ul>

<h3 id="对比总结">对比总结</h3>

<table>
  <thead>
    <tr>
      <th><strong>特性</strong></th>
      <th><strong>写法 1 (enable_if 偏特化)</strong></th>
      <th><strong>写法 2 (显式特化)</strong></th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td><strong>模板类型</strong></td>
      <td><strong>偏特化 (Partial)</strong></td>
      <td><strong>显式/全特化 (Explicit/Full)</strong></td>
    </tr>
    <tr>
      <td><strong>匹配方式</strong></td>
      <td><strong>基于规则</strong> (e.g., “is floating point”)</td>
      <td><strong>基于列表</strong> (e.g., “is <code class="language-plaintext highlighter-rouge">double</code>” OR “is <code class="language-plaintext highlighter-rouge">float</code>”)</td>
    </tr>
    <tr>
      <td><strong>泛化能力</strong></td>
      <td><strong>高</strong> (自动处理 <code class="language-plaintext highlighter-rouge">float</code>, <code class="language-plaintext highlighter-rouge">double</code>, <code class="language-plaintext highlighter-rouge">long double</code>)</td>
      <td><strong>低</strong> (必须分别为每个类型编写)</td>
    </tr>
    <tr>
      <td><strong>可维护性</strong></td>
      <td><strong>高</strong> (一份逻辑服务于一个类别)</td>
      <td><strong>低</strong> (易遗漏, 如 <code class="language-plaintext highlighter-rouge">long double</code>)</td>
    </tr>
  </tbody>
</table>

<p><strong>结论</strong>: 当您希望为<strong>一整类</strong>符合某个<strong>特征 (trait)</strong> 的类型提供统一实现时, 应使用 <code class="language-plaintext highlighter-rouge">std::enable_if</code> 进行偏特化. 当您只想为<strong>一两个特定类型</strong>提供完全定制的实现时, 才使用显式特化.</p>

<hr />

<h2 id="现代-c-的演进-超越-sfinae">现代 C++ 的演进: 超越 SFINAE</h2>

<p>虽然 SFINAE 和 <code class="language-plaintext highlighter-rouge">std::enable_if</code> 功能强大, 但它们语法晦涩, 且产生的错误信息往往令人难以理解. 现代 C++ 提供了更清晰的替代方案.</p>

<h3 id="c17-if-constexpr">C++17: <code class="language-plaintext highlighter-rouge">if constexpr</code></h3>

<p><code class="language-plaintext highlighter-rouge">if constexpr</code> 允许在函数体<em>内部</em>进行编译期分支. SFINAE 作用于函数<em>签名</em> (选择哪个函数) , 而 <code class="language-plaintext highlighter-rouge">if constexpr</code> 作用于函数<em>内部</em> (执行哪段代码) .</p>

<div class="language-cpp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// 替代模式二和三中的 destroy</span>
<span class="k">template</span><span class="o">&lt;</span><span class="k">class</span> <span class="nc">T</span><span class="p">&gt;</span>
<span class="kt">void</span> <span class="nf">destroy</span><span class="p">(</span><span class="n">T</span><span class="o">*</span> <span class="n">t</span><span class="p">)</span>
<span class="p">{</span>
    <span class="k">if</span> <span class="k">constexpr</span> <span class="p">(</span><span class="n">std</span><span class="o">::</span><span class="n">is_trivially_destructible_v</span><span class="o">&lt;</span><span class="n">T</span><span class="o">&gt;</span><span class="p">)</span> <span class="p">{</span>
        <span class="c1">// C++17 的 _v 助手, 等价于 ::value</span>
        <span class="n">std</span><span class="o">::</span><span class="n">cout</span> <span class="o">&lt;&lt;</span> <span class="s">"destroying trivially destructible T</span><span class="se">\n</span><span class="s">"</span><span class="p">;</span>
        <span class="c1">// (此分支为 true 时, else 分支根本不会被编译)</span>
    <span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
        <span class="n">std</span><span class="o">::</span><span class="n">cout</span> <span class="o">&lt;&lt;</span> <span class="s">"destroying non-trivially destructible T</span><span class="se">\n</span><span class="s">"</span><span class="p">;</span>
        <span class="n">t</span><span class="o">-&gt;~</span><span class="n">T</span><span class="p">();</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<p><code class="language-plaintext highlighter-rouge">if constexpr</code> 极大简化了在函数内部根据类型特性执行不同代码逻辑的场景, 不再需要复杂的重载集.</p>

<h3 id="c20-concepts-概念">C++20: Concepts (概念)</h3>

<p>Concepts 是对 SFINAE 机制的<strong>直接替代</strong>, 旨在从语言层面解决模板约束问题. 它提供了清晰, 易读的语法, 并能产生高质量的错误信息.</p>

<div class="language-cpp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// 替代模式一的 construct</span>
<span class="cp">#include</span> <span class="cpf">&lt;concepts&gt;</span><span class="c1"> // C++20</span><span class="cp">
</span>
<span class="k">template</span><span class="o">&lt;</span><span class="k">class</span> <span class="nc">T</span><span class="p">&gt;</span>
<span class="k">requires</span> <span class="n">std</span><span class="o">::</span><span class="n">is_trivially_default_constructible_v</span><span class="o">&lt;</span><span class="n">T</span><span class="o">&gt;</span>
<span class="kt">void</span> <span class="nf">construct</span><span class="p">(</span><span class="n">T</span><span class="o">*</span><span class="p">)</span> <span class="p">{</span>
    <span class="n">std</span><span class="o">::</span><span class="n">cout</span> <span class="o">&lt;&lt;</span> <span class="s">"default constructing trivially default constructible T</span><span class="se">\n</span><span class="s">"</span><span class="p">;</span>
<span class="p">}</span>

<span class="k">template</span><span class="o">&lt;</span><span class="k">class</span> <span class="nc">T</span><span class="p">&gt;</span>
<span class="k">requires</span> <span class="p">(</span><span class="o">!</span><span class="n">std</span><span class="o">::</span><span class="n">is_trivially_default_constructible_v</span><span class="o">&lt;</span><span class="n">T</span><span class="o">&gt;</span><span class="p">)</span>
<span class="kt">void</span> <span class="n">construct</span><span class="p">(</span><span class="n">T</span><span class="o">*</span> <span class="n">p</span><span class="p">)</span> <span class="p">{</span>
    <span class="n">std</span><span class="o">::</span><span class="n">cout</span> <span class="o">&lt;&lt;</span> <span class="s">"default constructing non-trivially default constructible T</span><span class="se">\n</span><span class="s">"</span><span class="p">;</span>
    <span class="o">::</span><span class="k">new</span><span class="p">(</span><span class="n">p</span><span class="p">)</span> <span class="n">T</span><span class="p">;</span>
<span class="p">}</span>
</code></pre></div></div>

<p><code class="language-plaintext highlighter-rouge">requires</code> 关键字清晰地表达了“此模板仅在满足…条件时才被启用”的意图, 这正是 <code class="language-plaintext highlighter-rouge">std::enable_if</code> 想要实现的目标, 但语法可读性提高了几个数量级.</p>

<h2 id="总结">总结</h2>

<p>SFINAE 是 C++ 模板系统中一个精巧的 (尽管有些晦涩) 规则, 它允许模板在重载解析期间根据类型属性安全地“退出”候选集. <code class="language-plaintext highlighter-rouge">std::enable_if</code> 是标准库提供的, 用于利用 SFINAE 规则的“触发器”.</p>

<p>通过在返回类型, 函数参数或模板参数中巧妙地使用 <code class="language-plaintext highlighter-rouge">std::enable_if</code>, 我们可以在编译期实现高度灵活的类型分派. 尽管 C++17 的 <code class="language-plaintext highlighter-rouge">if constexpr</code> 和 C++20 的 Concepts 提供了更优越的解决方案, 但理解 SFINAE 及其应用, 对于深入掌握 C++ 模板元编程, 阅读遗留代码以及理解现代 C++ 特性背后的演进动机, 仍然至关重要.</p>]]></content><author><name>Mikami</name></author><category term="cpp" /><category term="cpp" /><category term="code" /><summary type="html"><![CDATA[本文完整示例代码来源于 cppreference.com (example)]]></summary></entry><entry><title type="html">深入解析C++可变参数 - 模板从使用到核心原理</title><link href="https://mikami-w.github.io/cpp/2025/10/23/%E6%B7%B1%E5%85%A5%E8%A7%A3%E6%9E%90C++%E5%8F%AF%E5%8F%98%E5%8F%82%E6%95%B0_%E6%A8%A1%E6%9D%BF%E4%BB%8E%E4%BD%BF%E7%94%A8%E5%88%B0%E6%A0%B8%E5%BF%83%E5%8E%9F%E7%90%86.html" rel="alternate" type="text/html" title="深入解析C++可变参数 - 模板从使用到核心原理" /><published>2025-10-23T00:00:00+00:00</published><updated>2025-10-23T00:00:00+00:00</updated><id>https://mikami-w.github.io/cpp/2025/10/23/%E6%B7%B1%E5%85%A5%E8%A7%A3%E6%9E%90C++%E5%8F%AF%E5%8F%98%E5%8F%82%E6%95%B0_%E6%A8%A1%E6%9D%BF%E4%BB%8E%E4%BD%BF%E7%94%A8%E5%88%B0%E6%A0%B8%E5%BF%83%E5%8E%9F%E7%90%86</id><content type="html" xml:base="https://mikami-w.github.io/cpp/2025/10/23/%E6%B7%B1%E5%85%A5%E8%A7%A3%E6%9E%90C++%E5%8F%AF%E5%8F%98%E5%8F%82%E6%95%B0_%E6%A8%A1%E6%9D%BF%E4%BB%8E%E4%BD%BF%E7%94%A8%E5%88%B0%E6%A0%B8%E5%BF%83%E5%8E%9F%E7%90%86.html"><![CDATA[<p>C++11 引入的可变参数模板是现代C++泛型编程的基石之一. 它允许我们创建可以接受任意数量, 任意类型参数的模板函数和模板类. 这种能力在标准库中随处可见, 例如 <code class="language-plaintext highlighter-rouge">std::tuple</code>, <code class="language-plaintext highlighter-rouge">std::function</code>, <code class="language-plaintext highlighter-rouge">std::make_unique</code> 和 <code class="language-plaintext highlighter-rouge">std::vector::emplace_back</code>.</p>

<p>本文将分为两部分. 第一部分详细介绍 <code class="language-plaintext highlighter-rouge">...</code> 的核心语法和使用模式; 第二部分将深入分析编译器是如何处理 <code class="language-plaintext highlighter-rouge">...</code> 的, 探讨其背后的底层实现机制.</p>

<h2 id="第一部分--的使用方法">第一部分: <code class="language-plaintext highlighter-rouge">...</code> 的使用方法</h2>

<p><code class="language-plaintext highlighter-rouge">...</code> 在C++模板中有两种截然不同的角色: <strong>声明参数包</strong>和<strong>展开参数包</strong>.</p>

<h3 id="1-核心语法-声明参数包-parameter-pack">1. 核心语法: 声明参数包 (Parameter Pack)</h3>

<p>参数包是一个可以容纳零个或多个模板参数的“容器”.</p>

<h4 id="模板类型参数包-template-type-parameter-pack">模板类型参数包 (Template Type Parameter Pack)</h4>

<p>在模板参数列表中, <code class="language-plaintext highlighter-rouge">typename...</code> 或 <code class="language-plaintext highlighter-rouge">class...</code> 声明了一个“模板类型参数包”.</p>

<div class="language-cpp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// "Args" 是一个模板类型参数包, 代表 0 或多个类型. </span>
<span class="k">template</span><span class="o">&lt;</span><span class="k">typename</span><span class="o">...</span> <span class="nc">Args</span><span class="p">&gt;</span> 
<span class="k">class</span> <span class="nc">MyClass</span> <span class="p">{</span>
    <span class="c1">// ...</span>
<span class="p">};</span>

<span class="c1">// 用法示例:</span>
<span class="n">MyClass</span><span class="o">&lt;&gt;</span> <span class="n">obj1</span><span class="p">;</span>                <span class="c1">// Args = [] (空包)</span>
<span class="n">MyClass</span><span class="o">&lt;</span><span class="kt">int</span><span class="o">&gt;</span> <span class="n">obj2</span><span class="p">;</span>             <span class="c1">// Args = [int]</span>
<span class="n">MyClass</span><span class="o">&lt;</span><span class="kt">int</span><span class="p">,</span> <span class="kt">double</span><span class="p">,</span> <span class="kt">bool</span><span class="o">&gt;</span> <span class="n">obj3</span><span class="p">;</span> <span class="c1">// Args = [int, double, bool]</span>
</code></pre></div></div>

<h4 id="函数参数包-function-parameter-pack">函数参数包 (Function Parameter Pack)</h4>

<p>在函数参数列表中, <code class="language-plaintext highlighter-rouge">Args... args</code> 声明了一个“函数参数包”, 它由对应类型包<code class="language-plaintext highlighter-rouge">Args</code>中的所有类型构成.</p>

<div class="language-cpp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// "args" 是一个函数参数包, 代表 0 或多个函数参数. </span>
<span class="k">template</span><span class="o">&lt;</span><span class="k">typename</span><span class="o">...</span> <span class="nc">Args</span><span class="p">&gt;</span>
<span class="kt">void</span> <span class="nf">myFunction</span><span class="p">(</span><span class="n">Args</span><span class="p">...</span> <span class="n">args</span><span class="p">)</span> <span class="p">{</span>
    <span class="c1">// ...</span>
<span class="p">}</span>

<span class="n">myFunction</span><span class="p">();</span>             <span class="c1">// args = []</span>
<span class="n">myFunction</span><span class="p">(</span><span class="mi">1</span><span class="p">);</span>            <span class="c1">// args = [1]</span>
<span class="n">myFunction</span><span class="p">(</span><span class="mi">1</span><span class="p">,</span> <span class="s">"hello"</span><span class="p">);</span> <span class="c1">// args = [1, "hello"]</span>
</code></pre></div></div>

<h3 id="2-关键操作-展开参数包-pack-expansion">2. 关键操作: 展开参数包 (Pack Expansion)</h3>

<p>声明参数包后, 我们不能像遍历数组一样在运行时去迭代它. 参数包必须在<strong>编译期</strong>被“展开”. 展开的语法是在包名 (如 <code class="language-plaintext highlighter-rouge">args</code>) 或模式 (如 <code class="language-plaintext highlighter-rouge">std::forward&lt;Args&gt;(args)</code>) 后跟一个 <code class="language-plaintext highlighter-rouge">...</code>.</p>

<p>以下是几种最核心的展开模式.</p>

<h4 id="方法一-递归模板函数-c11-经典模式">方法一: 递归模板函数 (C++11 经典模式)</h4>

<p>这是理解参数包展开的基础. 我们定义一个递归的模板函数, 一次处理包中的一个参数, 然后用剩余的参数递归调用自身.</p>

<ul>
  <li><strong>递归步骤</strong>: 定义一个模板, 接受至少一个参数 <code class="language-plaintext highlighter-rouge">T first</code> 和一个参数包 <code class="language-plaintext highlighter-rouge">Args... rest</code>.</li>
  <li><strong>基本情况 (Base Case)</strong>: 定义一个同名的, 不接受参数 (或参数包为空) 的函数重载, 用于终止递归.</li>
</ul>

<p><strong>示例: 实现一个泛型的 <code class="language-plaintext highlighter-rouge">print</code> 函数</strong></p>

<div class="language-cpp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="cp">#include</span> <span class="cpf">&lt;iostream&gt;</span><span class="cp">
</span>
<span class="c1">// 1. 基本情况 (Base Case): 参数包为空时调用</span>
<span class="kt">void</span> <span class="nf">print</span><span class="p">()</span> <span class="p">{</span>
    <span class="n">std</span><span class="o">::</span><span class="n">cout</span> <span class="o">&lt;&lt;</span> <span class="n">std</span><span class="o">::</span><span class="n">endl</span><span class="p">;</span>
<span class="p">}</span>

<span class="c1">// 2. 递归步骤: </span>
<span class="c1">//    T 匹配第一个参数, Args... 匹配所有剩余参数</span>
<span class="k">template</span><span class="o">&lt;</span><span class="k">typename</span> <span class="nc">T</span><span class="p">,</span> <span class="k">typename</span><span class="o">...</span> <span class="nc">Args</span><span class="p">&gt;</span>
<span class="kt">void</span> <span class="n">print</span><span class="p">(</span><span class="n">T</span> <span class="n">first</span><span class="p">,</span> <span class="n">Args</span><span class="p">...</span> <span class="n">rest</span><span class="p">)</span> <span class="p">{</span>
    <span class="n">std</span><span class="o">::</span><span class="n">cout</span> <span class="o">&lt;&lt;</span> <span class="n">first</span> <span class="o">&lt;&lt;</span> <span class="s">" "</span><span class="p">;</span> <span class="c1">// 处理第一个参数</span>
    
    <span class="c1">// 展开 "rest" 包并递归调用</span>
    <span class="c1">// 如果 rest = [1, "hi"], 这里会被展开为 print(1, "hi");</span>
    <span class="n">print</span><span class="p">(</span><span class="n">rest</span><span class="p">...);</span>           
<span class="p">}</span>

<span class="kt">int</span> <span class="n">main</span><span class="p">()</span> <span class="p">{</span>
    <span class="n">print</span><span class="p">(</span><span class="s">"Value:"</span><span class="p">,</span> <span class="mi">10</span><span class="p">,</span> <span class="mf">3.14</span><span class="p">);</span>
    <span class="c1">// 输出: Value: 10 3.14 </span>
<span class="p">}</span>
</code></pre></div></div>

<h4 id="方法二-pattern-展开与完美转发-c11">方法二: <code class="language-plaintext highlighter-rouge">pattern...</code> 展开与完美转发 (C++11)</h4>

<p><code class="language-plaintext highlighter-rouge">...</code> 的一个极其重要的用途是<strong>完美转发 (Perfect Forwarding)</strong>. 它允许我们将参数以其原始的值类别 (左值或右值) 转发给另一个函数, 这在工厂函数和构造函数包装器中至关重要.</p>

<p>语法是 <code class="language-plaintext highlighter-rouge">pattern...</code>, 其中 <code class="language-plaintext highlighter-rouge">pattern</code> 是一个包含参数包的表达式.</p>

<p><strong>示例: 实现一个简化的 <code class="language-plaintext highlighter-rouge">make_unique</code></strong></p>

<div class="language-cpp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="cp">#include</span> <span class="cpf">&lt;memory&gt;</span><span class="cp">
#include</span> <span class="cpf">&lt;utility&gt;</span><span class="c1"> // for std::forward</span><span class="cp">
</span>
<span class="k">template</span><span class="o">&lt;</span><span class="k">typename</span> <span class="nc">T</span><span class="p">,</span> <span class="k">typename</span><span class="o">...</span> <span class="nc">Args</span><span class="p">&gt;</span>
<span class="n">std</span><span class="o">::</span><span class="n">unique_ptr</span><span class="o">&lt;</span><span class="n">T</span><span class="o">&gt;</span> <span class="n">make_my_unique</span><span class="p">(</span><span class="n">Args</span><span class="o">&amp;&amp;</span><span class="p">...</span> <span class="n">args</span><span class="p">)</span> <span class="p">{</span>
    <span class="c1">// 1. Args&amp;&amp;... args: </span>
    <span class="c1">//    这里的 "Args" 是模板参数包, "args" 是函数参数包. </span>
    <span class="c1">//    "Args&amp;&amp;" 是通用引用(Universal Reference)的包. </span>

    <span class="c1">// 2. 关键的展开: </span>
    <span class="c1">//    模式(pattern)是: std::forward&lt;Args&gt;(args)</span>
    <span class="c1">//    "..." 指示编译器将此模式应用到 "args" 包中的每一个元素, </span>
    <span class="c1">//    并用逗号分隔. </span>
    <span class="c1">//</span>
    <span class="c1">//    假设调用 make_my_unique&lt;MyType&gt;(1, "hi")</span>
    <span class="c1">//    "Args" 被推导为 [int, const char*]</span>
    <span class="c1">//    "args" 包包含 [1, "hi"]</span>
    <span class="c1">//    展开结果为: </span>
    <span class="c1">//        std::forward&lt;int&gt;(arg1), std::forward&lt;const char*&gt;(arg2)</span>
    <span class="c1">//</span>
    <span class="k">return</span> <span class="n">std</span><span class="o">::</span><span class="n">unique_ptr</span><span class="o">&lt;</span><span class="n">T</span><span class="o">&gt;</span><span class="p">(</span><span class="k">new</span> <span class="n">T</span><span class="p">(</span><span class="n">std</span><span class="o">::</span><span class="n">forward</span><span class="o">&lt;</span><span class="n">Args</span><span class="o">&gt;</span><span class="p">(</span><span class="n">args</span><span class="p">)...));</span>
<span class="p">}</span>
</code></pre></div></div>

<p>这种 <code class="language-plaintext highlighter-rouge">pattern...</code> 展开也常用于初始化列表, 例如 <code class="language-plaintext highlighter-rouge">std::array&lt;int, sizeof...(Args)&gt; { (args * 2)... };</code>.</p>

<h4 id="方法三-折叠表达式-c17">方法三: 折叠表达式 (C++17)</h4>

<p>C++17 引入了<strong>折叠表达式 (Fold Expressions)</strong>, 极大地简化了对参数包应用二元运算符的操作, 使递归不再是必需的.</p>

<p>它有四种形式, 最常用的是<strong>一元右折叠 (Unary Right Fold)</strong> <code class="language-plaintext highlighter-rouge">(pack ... op)</code>.</p>

<p><strong>示例 1: 计算所有参数的总和</strong></p>

<div class="language-cpp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">template</span><span class="o">&lt;</span><span class="k">typename</span><span class="o">...</span> <span class="nc">Args</span><span class="p">&gt;</span>
<span class="k">auto</span> <span class="nf">sum</span><span class="p">(</span><span class="n">Args</span><span class="p">...</span> <span class="n">args</span><span class="p">)</span> <span class="p">{</span>
    <span class="c1">// (args + ...): 一元右折叠</span>
    <span class="c1">// 如果 args = [1, 2, 3, 4]</span>
    <span class="c1">// 展开为: (1 + (2 + (3 + 4)))</span>
    <span class="k">return</span> <span class="p">(</span><span class="n">args</span> <span class="o">+</span> <span class="p">...);</span>
<span class="p">}</span>

<span class="kt">int</span> <span class="n">total</span> <span class="o">=</span> <span class="n">sum</span><span class="p">(</span><span class="mi">1</span><span class="p">,</span> <span class="mi">2</span><span class="p">,</span> <span class="mi">3</span><span class="p">,</span> <span class="mi">4</span><span class="p">,</span> <span class="mi">5</span><span class="p">);</span> <span class="c1">// 15</span>
</code></pre></div></div>

<p>*注意: 对于空包 <code class="language-plaintext highlighter-rouge">sum()</code>, <code class="language-plaintext highlighter-rouge">+</code> 运算符会编译失败. C++17 为此提供了 <code class="language-plaintext highlighter-rouge">(pack op ... op initial_value)</code> 的形式, 或在 C++20 中使用 <code class="language-plaintext highlighter-rouge">requires</code> 约束. *</p>

<p><strong>示例 2: 使用逗号运算符实现 <code class="language-plaintext highlighter-rouge">print</code> (C++17)</strong></p>

<p>我们可以利用<strong>逗号运算符</strong>的从左到右求值的特性, 在一行代码内实现 <code class="language-plaintext highlighter-rouge">print</code>.</p>

<div class="language-cpp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">template</span><span class="o">&lt;</span><span class="k">typename</span><span class="o">...</span> <span class="nc">Args</span><span class="p">&gt;</span>
<span class="kt">void</span> <span class="nf">print_modern</span><span class="p">(</span><span class="n">Args</span><span class="p">...</span> <span class="n">args</span><span class="p">)</span> <span class="p">{</span>
    <span class="c1">// "模式" 是 (std::cout &lt;&lt; args &lt;&lt; " ")</span>
    <span class="c1">// "运算符(op)" 是 , (逗号)</span>
    <span class="c1">// 展开为:</span>
    <span class="c1">// ((std::cout &lt;&lt; arg1 &lt;&lt; " "), ((std::cout &lt;&lt; arg2 &lt;&lt; " "), ...))</span>
    <span class="p">((</span><span class="n">std</span><span class="o">::</span><span class="n">cout</span> <span class="o">&lt;&lt;</span> <span class="n">args</span> <span class="o">&lt;&lt;</span> <span class="s">" "</span><span class="p">),</span> <span class="p">...);</span>
    <span class="n">std</span><span class="o">::</span><span class="n">cout</span> <span class="o">&lt;&lt;</span> <span class="n">std</span><span class="o">::</span><span class="n">endl</span><span class="p">;</span>
<span class="p">}</span>
</code></pre></div></div>

<h3 id="3-辅助工具-sizeof">3. 辅助工具: <code class="language-plaintext highlighter-rouge">sizeof...</code></h3>

<p>要获取参数包中的元素个数, 可以使用 <code class="language-plaintext highlighter-rouge">sizeof...</code> 运算符.</p>

<div class="language-cpp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">template</span><span class="o">&lt;</span><span class="k">typename</span><span class="o">...</span> <span class="nc">Args</span><span class="p">&gt;</span>
<span class="kt">size_t</span> <span class="nf">count</span><span class="p">(</span><span class="n">Args</span><span class="p">...</span> <span class="n">args</span><span class="p">)</span> <span class="p">{</span>
    <span class="k">return</span> <span class="k">sizeof</span><span class="p">...(</span><span class="n">Args</span><span class="p">);</span> <span class="c1">// 推荐</span>
    <span class="c1">// return sizeof...(args); // 也可以</span>
<span class="p">}</span>

<span class="kt">size_t</span> <span class="n">c</span> <span class="o">=</span> <span class="n">count</span><span class="p">(</span><span class="mi">1</span><span class="p">,</span> <span class="mf">2.5</span><span class="p">,</span> <span class="s">"three"</span><span class="p">);</span> <span class="c1">// c == 3</span>
</code></pre></div></div>

<hr />

<h2 id="第二部分--的底层原理">第二部分: <code class="language-plaintext highlighter-rouge">...</code> 的底层原理</h2>

<p><code class="language-plaintext highlighter-rouge">...</code> 并不是一个运行时机制. 它的所有魔力都发生在<strong>编译期</strong>, 属于<strong>模板元编程 (Template Metaprogramming, TMP)</strong> 的范畴. 编译器会根据 <code class="language-plaintext highlighter-rouge">...</code> 指令<strong>生成</strong>特定的代码.</p>

<p>以下是 <code class="language-plaintext highlighter-rouge">...</code> 的几种核心工作机制.</p>

<h3 id="机制一-递归模板实例化-c1114">机制一: 递归模板实例化 (C++11/14)</h3>

<p>这是C++11中最核心的机制, 它在编译期模拟了递归. 我们以 <code class="language-plaintext highlighter-rouge">print</code> 函数为例, 分析编译器的“视角”.</p>

<p>当编译器遇到 <code class="language-plaintext highlighter-rouge">print("Hello", 1, 3.14);</code> 调用时:</p>

<ol>
  <li>
    <p><strong>第一次实例化</strong>:</p>

    <ul>
      <li>
        <p>编译器匹配到 <code class="language-plaintext highlighter-rouge">print&lt;T, typename... Args&gt;</code>.</p>
      </li>
      <li>
        <p>推导 <code class="language-plaintext highlighter-rouge">T = const char*</code>, <code class="language-plaintext highlighter-rouge">Args = [int, double]</code>.</p>
      </li>
      <li>
        <p>编译器<strong>生成</strong>一个新函数 (我们称之为 <code class="language-plaintext highlighter-rouge">print_1</code>) :</p>

        <div class="language-cpp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// 编译器内部生成的函数 #1</span>
<span class="kt">void</span> <span class="nf">print_1</span><span class="p">(</span><span class="k">const</span> <span class="kt">char</span><span class="o">*</span> <span class="n">first</span><span class="p">,</span> <span class="kt">int</span> <span class="n">rest_arg1</span><span class="p">,</span> <span class="kt">double</span> <span class="n">rest_arg2</span><span class="p">)</span> <span class="p">{</span>
    <span class="n">std</span><span class="o">::</span><span class="n">cout</span> <span class="o">&lt;&lt;</span> <span class="n">first</span> <span class="o">&lt;&lt;</span> <span class="s">" "</span><span class="p">;</span>
    <span class="n">print</span><span class="p">(</span><span class="n">rest_arg1</span><span class="p">,</span> <span class="n">rest_arg2</span><span class="p">);</span> <span class="c1">// 展开 "rest..." </span>
<span class="p">}</span>
</code></pre></div>        </div>
      </li>
    </ul>
  </li>
  <li>
    <p><strong>第二次实例化</strong>:</p>

    <ul>
      <li>
        <p>在编译 <code class="language-plaintext highlighter-rouge">print_1</code> 时, 编译器遇到了新调用 <code class="language-plaintext highlighter-rouge">print(1, 3.14);</code>.</p>
      </li>
      <li>
        <p>它再次匹配 <code class="language-plaintext highlighter-rouge">print&lt;T, typename... Args&gt;</code>.</p>
      </li>
      <li>
        <p>推导 <code class="language-plaintext highlighter-rouge">T = int</code>, <code class="language-plaintext highlighter-rouge">Args = [double]</code>.</p>
      </li>
      <li>
        <p>编译器<strong>生成</strong>第二个函数 (<code class="language-plaintext highlighter-rouge">print_2</code>) :</p>

        <div class="language-cpp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// 编译器内部生成的函数 #2</span>
<span class="kt">void</span> <span class="nf">print_2</span><span class="p">(</span><span class="kt">int</span> <span class="n">first</span><span class="p">,</span> <span class="kt">double</span> <span class="n">rest_arg1</span><span class="p">)</span> <span class="p">{</span>
    <span class="n">std</span><span class="o">::</span><span class="n">cout</span> <span class="o">&lt;&lt;</span> <span class="n">first</span> <span class="o">&lt;&lt;</span> <span class="s">" "</span><span class="p">;</span>
    <span class="n">print</span><span class="p">(</span><span class="n">rest_arg1</span><span class="p">);</span> <span class="c1">// 展开 "rest..."</span>
<span class="p">}</span>
</code></pre></div>        </div>
      </li>
    </ul>
  </li>
  <li>
    <p><strong>第三次实例化</strong>:</p>

    <ul>
      <li>
        <p>在编译 <code class="language-plaintext highlighter-rouge">print_2</code> 时, 编译器遇到了 <code class="language-plaintext highlighter-rouge">print(3.14);</code>.</p>
      </li>
      <li>
        <p>推导 <code class="language-plaintext highlighter-rouge">T = double</code>, <code class="language-plaintext highlighter-rouge">Args = []</code> (空包).</p>
      </li>
      <li>
        <p>编译器<strong>生成</strong>第三个函数 (<code class="language-plaintext highlighter-rouge">print_3</code>) :</p>

        <div class="language-cpp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// 编译器内部生成的函数 #3</span>
<span class="kt">void</span> <span class="nf">print_3</span><span class="p">(</span><span class="kt">double</span> <span class="n">first</span><span class="p">)</span> <span class="p">{</span>
    <span class="n">std</span><span class="o">::</span><span class="n">cout</span> <span class="o">&lt;&lt;</span> <span class="n">first</span> <span class="o">&lt;&lt;</span> <span class="s">" "</span><span class="p">;</span>
    <span class="n">print</span><span class="p">();</span> <span class="c1">// 展开 "rest..." (空包)</span>
<span class="p">}</span>
</code></pre></div>        </div>
      </li>
    </ul>
  </li>
  <li>
    <p><strong>递归终止</strong>:</p>

    <ul>
      <li>在编译 <code class="language-plaintext highlighter-rouge">print_3</code> 时, 编译器遇到了 <code class="language-plaintext highlighter-rouge">print();</code>.</li>
      <li>此时, 模板函数不再是最佳匹配 (因为它至少需要一个 <code class="language-plaintext highlighter-rouge">T</code>) .</li>
      <li>编译器选择 <code class="language-plaintext highlighter-rouge">void print()</code> 这个非模板的<strong>基本情况 (Base Case)</strong>.</li>
      <li>递归实例化结束.</li>
    </ul>
  </li>
</ol>

<p><strong>原理总结</strong>: <code class="language-plaintext highlighter-rouge">...</code> 驱动了编译器的<strong>递归模板实例化</strong>过程. 最终的可执行文件中并没有“循环”, 而是包含了一系列被静态链接起来的, 特化 (或实例化) 的函数调用链.</p>

<h3 id="机制二-pattern-原地展开">机制二: <code class="language-plaintext highlighter-rouge">pattern...</code> 原地展开</h3>

<p>当编译器遇到 <code class="language-plaintext highlighter-rouge">pattern...</code> 语法时 (如 <code class="language-plaintext highlighter-rouge">std::forward&lt;Args&gt;(args)...</code>) , 它会扮演一个“代码复读机”的角色.</p>

<p>它在抽象语法树 (AST) 中识别出这个模式, 然后将其“水平地”展开, 为参数包中的每一个元素生成一份代码, 并用逗号 <code class="language-plaintext highlighter-rouge">,</code> 分隔.</p>

<div class="language-cpp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// 模板代码:</span>
<span class="k">new</span> <span class="nf">T</span><span class="p">(</span><span class="n">std</span><span class="o">::</span><span class="n">forward</span><span class="o">&lt;</span><span class="n">Args</span><span class="o">&gt;</span><span class="p">(</span><span class="n">args</span><span class="p">)...);</span>

<span class="c1">// 假设 Args = [int, double]</span>
<span class="c1">// 编译器在 AST 中将其重写(rewrite)为:</span>
<span class="k">new</span> <span class="nf">T</span><span class="p">(</span><span class="n">std</span><span class="o">::</span><span class="n">forward</span><span class="o">&lt;</span><span class="kt">int</span><span class="o">&gt;</span><span class="p">(</span><span class="n">arg1</span><span class="p">),</span> <span class="n">std</span><span class="o">::</span><span class="n">forward</span><span class="o">&lt;</span><span class="kt">double</span><span class="o">&gt;</span><span class="p">(</span><span class="n">arg2</span><span class="p">));</span>
</code></pre></div></div>

<p>这个机制常用于函数调用, 初始化列表和基类列表.</p>

<h3 id="机制三-折叠表达式的ast转换-c17">机制三: 折叠表达式的AST转换 (C++17)</h3>

<p>C++17的折叠表达式是一种更高级的机制. 它让编译器不再需要通过递归实例化来“折叠”参数包, 而是直接在AST上进行<strong>树状结构转换</strong>.</p>

<div class="language-cpp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// 模板代码:</span>
<span class="k">return</span> <span class="p">(</span><span class="n">args</span> <span class="o">+</span> <span class="p">...);</span> <span class="c1">// 一元右折叠</span>

<span class="c1">// 假设 args = [1, 2, 3]</span>
<span class="c1">// 编译器在 AST 中直接将其重写为: </span>
<span class="k">return</span> <span class="p">(</span><span class="mi">1</span> <span class="o">+</span> <span class="p">(</span><span class="mi">2</span> <span class="o">+</span> <span class="mi">3</span><span class="p">));</span>
</code></pre></div></div>

<p>这比递归实例化更简洁, 编译速度可能更快, 并且意图更明显. <code class="language-plaintext highlighter-rouge">print_modern</code> 中对逗号运算符的折叠也是同理.</p>

<h3 id="机制四-递归继承与数据结构">机制四: 递归继承与数据结构</h3>

<p><code class="language-plaintext highlighter-rouge">...</code> 不仅用于函数, 还用于类. <code class="language-plaintext highlighter-rouge">std::tuple</code> 是如何存储任意数量和类型的成员的？答案是<strong>递归继承</strong>.</p>

<p>一个简化的 <code class="language-plaintext highlighter-rouge">Tuple</code> 实现如下:</p>

<div class="language-cpp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// 基本情况: 空 Tuple</span>
<span class="k">template</span> <span class="o">&lt;</span><span class="k">typename</span><span class="o">...</span> <span class="nc">Types</span><span class="p">&gt;</span>
<span class="k">class</span> <span class="nc">Tuple</span><span class="p">;</span>

<span class="k">template</span> <span class="o">&lt;</span><span class="p">&gt;</span>
<span class="k">class</span> <span class="nc">Tuple</span><span class="o">&lt;&gt;</span> <span class="p">{};</span> <span class="c1">// 0个元素的 Tuple</span>

<span class="c1">// 递归步骤</span>
<span class="k">template</span> <span class="o">&lt;</span><span class="k">typename</span> <span class="nc">Head</span><span class="p">,</span> <span class="k">typename</span><span class="o">...</span> <span class="nc">Tail</span><span class="p">&gt;</span>
<span class="k">class</span> <span class="nc">Tuple</span><span class="o">&lt;</span><span class="n">Head</span><span class="p">,</span> <span class="n">Tail</span><span class="p">...</span><span class="o">&gt;</span> <span class="o">:</span> <span class="k">private</span> <span class="n">Tuple</span><span class="o">&lt;</span><span class="n">Tail</span><span class="p">...</span><span class="o">&gt;</span> <span class="c1">// 继承"尾巴"</span>
<span class="p">{</span>
<span class="nl">public:</span>
    <span class="c1">// ... 构造函数 ...</span>
    <span class="n">Head</span> <span class="n">m_head</span><span class="p">;</span> <span class="c1">// 存储包中的第一个元素</span>
<span class="p">};</span>
</code></pre></div></div>

<p>当编译器看到 <code class="language-plaintext highlighter-rouge">Tuple&lt;int, double, char&gt;</code> 时, 它会生成一个类继承链:</p>

<ol>
  <li><code class="language-plaintext highlighter-rouge">Tuple&lt;int, double, char&gt;</code>
    <ul>
      <li>有一个成员 <code class="language-plaintext highlighter-rouge">int m_head</code></li>
      <li>继承自 <code class="language-plaintext highlighter-rouge">Tuple&lt;double, char&gt;</code></li>
    </ul>
  </li>
  <li><code class="language-plaintext highlighter-rouge">Tuple&lt;double, char&gt;</code>
    <ul>
      <li>有一个成员 <code class="language-plaintext highlighter-rouge">double m_head</code></li>
      <li>继承自 <code class="language-plaintext highlighter-rouge">Tuple&lt;char&gt;</code></li>
    </ul>
  </li>
  <li><code class="language-plaintext highlighter-rouge">Tuple&lt;char&gt;</code>
    <ul>
      <li>有一个成员 <code class="language-plaintext highlighter-rouge">char m_head</code></li>
      <li>继承自 <code class="language-plaintext highlighter-rouge">Tuple&lt;&gt;</code> (空基类)</li>
    </ul>
  </li>
</ol>

<p><strong>原理总结</strong>: <code class="language-plaintext highlighter-rouge">...</code> 在类模板中驱动了<strong>编译期的递归继承</strong>. 编译器为你生成了一系列嵌套的类定义, 巧妙地将一个“包含N个成员的类”在内存中表示为 N 个“各自包含1个成员的类”的继承链. 空基类优化 (Empty Base Class Optimization, EBCO) 会确保 <code class="language-plaintext highlighter-rouge">Tuple&lt;&gt;</code> 基类不占用任何空间.</p>

<h2 id="结论">结论</h2>

<p>C++ 中的 <code class="language-plaintext highlighter-rouge">...</code> 是一个强大的<strong>模板元编程</strong>工具. 它为编译器提供了指令, 使其能够在编译时通过<strong>递归实例化</strong>, <strong>原地展开</strong>, <strong>AST转换</strong>和<strong>递归继承</strong>等机制来自动生成高度特化和泛型的代码. 理解这些底层原理, 是精通现代C++泛型编程的关键.</p>]]></content><author><name>Mikami</name></author><category term="cpp" /><category term="cpp" /><category term="code" /><summary type="html"><![CDATA[C++11 引入的可变参数模板是现代C++泛型编程的基石之一. 它允许我们创建可以接受任意数量, 任意类型参数的模板函数和模板类. 这种能力在标准库中随处可见, 例如 std::tuple, std::function, std::make_unique 和 std::vector::emplace_back.]]></summary></entry><entry><title type="html">C++头文件</title><link href="https://mikami-w.github.io/cpp/2025/10/22/C++%E5%A4%B4%E6%96%87%E4%BB%B6charconv.html" rel="alternate" type="text/html" title="C++头文件" /><published>2025-10-22T00:00:00+00:00</published><updated>2025-10-22T00:00:00+00:00</updated><id>https://mikami-w.github.io/cpp/2025/10/22/C++%E5%A4%B4%E6%96%87%E4%BB%B6charconv</id><content type="html" xml:base="https://mikami-w.github.io/cpp/2025/10/22/C++%E5%A4%B4%E6%96%87%E4%BB%B6charconv.html"><![CDATA[<p><code class="language-plaintext highlighter-rouge">&lt;charconv&gt;</code> 是 C++17 标准库中引入的一个非常重要且实用的头文件. 它的核心目标是提供<strong>高性能, 无异常, 无内存分配, 且区域设置(locale)无关</strong>的<strong>基本数值类型与字符串之间的相互转换</strong>.</p>

<p>在 <code class="language-plaintext highlighter-rouge">&lt;charconv&gt;</code> 出现之前, C++ 开发者通常有以下几种选择, 但它们各有缺点:</p>

<ol>
  <li><strong>C 风格 (stdio):</strong> <code class="language-plaintext highlighter-rouge">sprintf</code>, <code class="language-plaintext highlighter-rouge">sscanf</code>, <code class="language-plaintext highlighter-rouge">atoi</code>, <code class="language-plaintext highlighter-rouge">atof</code> 等.
    <ul>
      <li><strong>缺点:</strong> 类型不安全, 容易导致缓冲区溢出 (尤其是 <code class="language-plaintext highlighter-rouge">sprintf</code>) , 并且 <code class="language-plaintext highlighter-rouge">sscanf</code> 的性能不佳.</li>
    </ul>
  </li>
  <li><strong>C++ 字符串流 (sstream):</strong> <code class="language-plaintext highlighter-rouge">std::stringstream</code>.
    <ul>
      <li><strong>缺点:</strong> 性能开销大, 涉及动态内存分配, 并且受本地化 (locale) 影响 (例如, 某些地区用 <code class="language-plaintext highlighter-rouge">,</code> 作小数点) .</li>
    </ul>
  </li>
  <li><strong>C++11 (string):</strong> <code class="language-plaintext highlighter-rouge">std::stoi</code>, <code class="language-plaintext highlighter-rouge">std::stod</code>, <code class="language-plaintext highlighter-rouge">std::to_string</code> 等.
    <ul>
      <li><strong>缺点:</strong>
        <ul>
          <li>它们会<strong>抛出异常</strong> ( <code class="language-plaintext highlighter-rouge">std::invalid_argument</code>, <code class="language-plaintext highlighter-rouge">std::out_of_range</code> ), 这在性能敏感的代码中 (如游戏循环, 高频交易) 可能是不可接受的开销.</li>
          <li><code class="language-plaintext highlighter-rouge">std::to_string</code> 内部<strong>可能进行内存分配</strong>.</li>
          <li>它们仍然受本地化 (locale) 影响.</li>
        </ul>
      </li>
    </ul>
  </li>
</ol>

<p><code class="language-plaintext highlighter-rouge">&lt;charconv&gt;</code> 旨在彻底解决以上所有痛点.</p>

<hr />

<h3 id="charconv-的核心特性"><code class="language-plaintext highlighter-rouge">&lt;charconv&gt;</code> 的核心特性</h3>

<ol>
  <li>
    <p>高性能 (High Performance):</p>

    <p>它的实现被高度优化, 通常是 C++ 中最快的数字-字符串转换方式, 甚至快于 C 风格的 itoa (非标准) 或 sprintf.</p>
  </li>
  <li>
    <p>无内存分配 (Non-allocating):</p>

    <p>所有操作都在用户提供的固定大小的字符缓冲区上进行. 它不会在堆上分配任何内存.</p>
  </li>
  <li>
    <p>无异常 (Non-throwing):</p>

    <p>它通过返回一个包含错误码的 struct 来报告成功或失败, 而不是抛出异常. 这使得错误处理的开销极低.</p>
  </li>
  <li>
    <p>区域设置无关 (Locale-independent):</p>

    <p>它始终使用 “C” locale 规则 (例如, 小数点总是 ‘.’) . 这对于配置文件, JSON, XML, 网络协议等需要固定格式的数据序列化和反序列化至关重要.</p>
  </li>
</ol>

<hr />

<h3 id="两个核心函数">两个核心函数</h3>

<p><code class="language-plaintext highlighter-rouge">&lt;charconv&gt;</code> 只提供了两个核心函数模板 (及其重载) :</p>

<ol>
  <li><strong><code class="language-plaintext highlighter-rouge">std::to_chars</code></strong>: 将数值转换为字符串.</li>
  <li><strong><code class="language-plaintext highlighter-rouge">std::from_chars</code></strong>: 将字符串转换为数值.</li>
</ol>

<p>它们都通过返回一个 <code class="language-plaintext highlighter-rouge">struct</code> 来报告操作结果.</p>

<hr />

<h3 id="1-stdto_chars-数值---字符串">1. <code class="language-plaintext highlighter-rouge">std::to_chars</code> (数值 -&gt; 字符串)</h3>

<p>它尝试将一个数值格式化后写入你提供的字符缓冲区 <code class="language-plaintext highlighter-rouge">[first, last)</code> 中.</p>

<h4 id="函数签名-整数">函数签名 (整数)</h4>

<div class="language-cpp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">std</span><span class="o">::</span><span class="n">to_chars_result</span> <span class="n">std</span><span class="o">::</span><span class="n">to_chars</span><span class="p">(</span>
    <span class="kt">char</span><span class="o">*</span> <span class="n">first</span><span class="p">,</span>          <span class="c1">// 缓冲区的起始指针</span>
    <span class="kt">char</span><span class="o">*</span> <span class="n">last</span><span class="p">,</span>           <span class="c1">// 缓冲区的末尾指针 (one-past-the-end)</span>
    <span class="n">T</span> <span class="n">value</span><span class="p">,</span>              <span class="c1">// 要转换的数值</span>
    <span class="kt">int</span> <span class="n">base</span> <span class="o">=</span> <span class="mi">10</span>         <span class="c1">// 进制 (支持 2 到 36)</span>
<span class="p">);</span>
</code></pre></div></div>

<ul>
  <li><strong><code class="language-plaintext highlighter-rouge">T</code></strong>: 可以是 <code class="language-plaintext highlighter-rouge">int</code>, <code class="language-plaintext highlighter-rouge">long</code>, <code class="language-plaintext highlighter-rouge">unsigned int</code>, <code class="language-plaintext highlighter-rouge">unsigned long long</code> 等各种整数类型.</li>
</ul>

<h4 id="返回值-stdto_chars_result">返回值: <code class="language-plaintext highlighter-rouge">std::to_chars_result</code></h4>

<p>这是一个 <code class="language-plaintext highlighter-rouge">struct</code>, 定义大致如下:</p>

<div class="language-cpp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">struct</span> <span class="nc">std</span><span class="o">::</span><span class="n">to_chars_result</span> <span class="p">{</span>
    <span class="kt">char</span><span class="o">*</span> <span class="n">ptr</span><span class="p">;</span>          <span class="c1">// 指向写入的最后一个字符的 *下一个* 位置</span>
    <span class="n">std</span><span class="o">::</span><span class="n">errc</span> <span class="n">ec</span><span class="p">;</span>       <span class="c1">// 错误码</span>
<span class="p">};</span>
</code></pre></div></div>

<ul>
  <li>如果 <code class="language-plaintext highlighter-rouge">ec</code> 是 <code class="language-plaintext highlighter-rouge">std::errc()</code> (即默认构造, 表示成功), 则 <code class="language-plaintext highlighter-rouge">[first, ptr)</code> 范围内就是转换后的字符串.</li>
  <li>如果 <code class="language-plaintext highlighter-rouge">ec</code> 是 <code class="language-plaintext highlighter-rouge">std::errc::value_too_large</code>, 表示你提供的缓冲区 <code class="language-plaintext highlighter-rouge">[first, last)</code> 不够大, 无法容纳转换后的字符串.</li>
</ul>

<hr />

<h3 id="2-stdfrom_chars-字符串---数值">2. <code class="language-plaintext highlighter-rouge">std::from_chars</code> (字符串 -&gt; 数值)</h3>

<p>它尝试从字符缓冲区 <code class="language-plaintext highlighter-rouge">[first, last)</code> 中解析一个数值.</p>

<h4 id="函数签名-整数-1">函数签名 (整数)</h4>

<div class="language-cpp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">std</span><span class="o">::</span><span class="n">from_chars_result</span> <span class="n">std</span><span class="o">::</span><span class="n">from_chars</span><span class="p">(</span>
    <span class="k">const</span> <span class="kt">char</span><span class="o">*</span> <span class="n">first</span><span class="p">,</span>    <span class="c1">// 要解析的字符串的起始指针</span>
    <span class="k">const</span> <span class="kt">char</span><span class="o">*</span> <span class="n">last</span><span class="p">,</span>     <span class="c1">// 要解析的字符串的末尾指针</span>
    <span class="n">T</span><span class="o">&amp;</span> <span class="n">value</span><span class="p">,</span>             <span class="c1">// [out] 用于接收解析结果的变量</span>
    <span class="kt">int</span> <span class="n">base</span> <span class="o">=</span> <span class="mi">10</span>         <span class="c1">// 进制 (支持 2 到 36)</span>
<span class="p">);</span>
</code></pre></div></div>

<ul>
  <li><strong><code class="language-plaintext highlighter-rouge">T</code></strong>: 必须是一个整数类型.</li>
</ul>

<h4 id="返回值-stdfrom_chars_result">返回值: <code class="language-plaintext highlighter-rouge">std::from_chars_result</code></h4>

<p>定义大致如下:</p>

<div class="language-cpp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">struct</span> <span class="nc">std</span><span class="o">::</span><span class="n">from_chars_result</span> <span class="p">{</span>
    <span class="k">const</span> <span class="kt">char</span><span class="o">*</span> <span class="n">ptr</span><span class="p">;</span>    <span class="c1">// 指向 *未被* 解析的第一个字符</span>
    <span class="n">std</span><span class="o">::</span><span class="n">errc</span> <span class="n">ec</span><span class="p">;</span>       <span class="c1">// 错误码</span>
<span class="p">};</span>
</code></pre></div></div>

<ul>
  <li>如果 <code class="language-plaintext highlighter-rouge">ec</code> 是 <code class="language-plaintext highlighter-rouge">std::errc()</code> (成功):
    <ul>
      <li><code class="language-plaintext highlighter-rouge">value</code> 中存储了T-sl” &gt;解析到的值.</li>
      <li><code class="language-plaintext highlighter-rouge">ptr</code> 指向第一个无法解析的字符. 例如, 解析 “12345abc”, <code class="language-plaintext highlighter-rouge">ptr</code> 会指向 ‘a’.</li>
    </ul>
  </li>
  <li>如果 <code class="language-plaintext highlighter-rouge">ec</code> 是 <code class="language-plaintext highlighter-rouge">std::errc::invalid_argument</code>:
    <ul>
      <li>表示在 <code class="language-plaintext highlighter-rouge">[first, last)</code> 范围内找不到任何有效的数字模式.</li>
    </ul>
  </li>
  <li>如果 <code class="language-plaintext highlighter-rouge">ec</code> 是 <code class="language-plaintext highlighter-rouge">std::errc::result_out_of_range</code>:
    <ul>
      <li>表示解析到的数字超出了类型 <code class="language-plaintext highlighter-rouge">T</code> 所能表示的范围 (上溢或下溢) .</li>
    </ul>
  </li>
</ul>

<hr />

<h3 id="浮点数支持">浮点数支持</h3>

<p><code class="language-plaintext highlighter-rouge">std::to_chars</code> 和 <code class="language-plaintext highlighter-rouge">std::from_chars</code> 同样重载了 <code class="language-plaintext highlighter-rouge">float</code>, <code class="language-plaintext highlighter-rouge">double</code> 和 <code class="language-plaintext highlighter-rouge">long double</code>.</p>

<p>浮点数版本的函数签名略有不同, 它们接受一个 <code class="language-plaintext highlighter-rouge">std::chars_format</code> 枚举来指定格式 (如科学计数法, 固定小数点等) , 并且 (目前) 不支持指定 <code class="language-plaintext highlighter-rouge">base</code> (总是 10 进制) .</p>

<div class="language-cpp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// 浮点数 to_chars 示例</span>
<span class="n">std</span><span class="o">::</span><span class="n">to_chars_result</span> <span class="n">std</span><span class="o">::</span><span class="n">to_chars</span><span class="p">(</span>
    <span class="kt">char</span><span class="o">*</span> <span class="n">first</span><span class="p">,</span> <span class="kt">char</span><span class="o">*</span> <span class="n">last</span><span class="p">,</span>
    <span class="kt">double</span> <span class="n">value</span><span class="p">,</span>
    <span class="n">std</span><span class="o">::</span><span class="n">chars_format</span> <span class="n">fmt</span> <span class="o">=</span> <span class="n">std</span><span class="o">::</span><span class="n">chars_format</span><span class="o">::</span><span class="n">general</span><span class="p">,</span>
    <span class="kt">int</span> <span class="n">precision</span> <span class="o">=</span> <span class="o">-</span><span class="mi">1</span> <span class="c1">// 默认精度</span>
<span class="p">);</span>

<span class="c1">// 浮点数 from_chars 示例</span>
<span class="n">std</span><span class="o">::</span><span class="n">from_chars_result</span> <span class="n">std</span><span class="o">::</span><span class="n">from_chars</span><span class="p">(</span>
    <span class="k">const</span> <span class="kt">char</span><span class="o">*</span> <span class="n">first</span><span class="p">,</span> <span class="k">const</span> <span class="kt">char</span><span class="o">*</span> <span class="n">last</span><span class="p">,</span>
    <span class="kt">double</span><span class="o">&amp;</span> <span class="n">value</span><span class="p">,</span>
    <span class="n">std</span><span class="o">::</span><span class="n">chars_format</span> <span class="n">fmt</span> <span class="o">=</span> <span class="n">std</span><span class="o">::</span><span class="n">chars_format</span><span class="o">::</span><span class="n">general</span>
<span class="p">);</span>
</code></pre></div></div>

<p><code class="language-plaintext highlighter-rouge">std::chars_format</code> 的值包括:</p>

<ul>
  <li><code class="language-plaintext highlighter-rouge">std::chars_format::general</code> (默认, 结合了 fixed 和 scientific)</li>
  <li><code class="language-plaintext highlighter-rouge">std::chars_format::fixed</code> (定点表示)</li>
  <li><code class="language-plaintext highlighter-rouge">std::chars_format::scientific</code> (科学计数法)</li>
  <li><code class="language-plaintext highlighter-rouge">std::chars_format::hex</code> (十六进制浮点数)</li>
</ul>

<hr />

<h3 id="完整示例代码">完整示例代码</h3>

<p>下面是一个结合使用 C++17 的 <code class="language-plaintext highlighter-rouge">std::string_view</code> 和结构化绑定 (Structured Bindings) 的现代 C++ 示例:</p>

<div class="language-cpp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="cp">#include</span> <span class="cpf">&lt;iostream&gt;</span><span class="cp">
#include</span> <span class="cpf">&lt;charconv&gt;</span><span class="c1">     // 核心头文件</span><span class="cp">
#include</span> <span class="cpf">&lt;string&gt;</span><span class="cp">
#include</span> <span class="cpf">&lt;string_view&gt;</span><span class="cp">
#include</span> <span class="cpf">&lt;system_error&gt;</span><span class="c1"> // std::errc</span><span class="cp">
#include</span> <span class="cpf">&lt;array&gt;</span><span class="c1">        // std::array</span><span class="cp">
</span>
<span class="c1">// 辅助函数: 将 std::errc 转换为字符串以便打印</span>
<span class="n">std</span><span class="o">::</span><span class="n">string</span> <span class="nf">error_to_string</span><span class="p">(</span><span class="n">std</span><span class="o">::</span><span class="n">errc</span> <span class="n">ec</span><span class="p">)</span> <span class="p">{</span>
    <span class="k">if</span> <span class="p">(</span><span class="n">ec</span> <span class="o">==</span> <span class="n">std</span><span class="o">::</span><span class="n">errc</span><span class="p">())</span> <span class="p">{</span>
        <span class="k">return</span> <span class="s">"Success"</span><span class="p">;</span>
    <span class="p">}</span>
    <span class="k">return</span> <span class="n">std</span><span class="o">::</span><span class="n">make_error_code</span><span class="p">(</span><span class="n">ec</span><span class="p">).</span><span class="n">message</span><span class="p">();</span>
<span class="p">}</span>

<span class="kt">int</span> <span class="n">main</span><span class="p">()</span> <span class="p">{</span>
    <span class="c1">// ------------------------------------------------</span>
    <span class="c1">// 示例 1: std::to_chars (int -&gt; string)</span>
    <span class="c1">// ------------------------------------------------</span>
    <span class="n">std</span><span class="o">::</span><span class="n">cout</span> <span class="o">&lt;&lt;</span> <span class="s">"--- std::to_chars ---"</span> <span class="o">&lt;&lt;</span> <span class="n">std</span><span class="o">::</span><span class="n">endl</span><span class="p">;</span>
    <span class="kt">int</span> <span class="n">my_value</span> <span class="o">=</span> <span class="mi">1989</span><span class="p">;</span>
    
    <span class="c1">// 必须提供一个缓冲区. std::array 是一个很好的选择. </span>
    <span class="c1">// 整数最多约20位 (64位) + 负号, 30 足够了. </span>
    <span class="n">std</span><span class="o">::</span><span class="n">array</span><span class="o">&lt;</span><span class="kt">char</span><span class="p">,</span> <span class="mi">30</span><span class="o">&gt;</span> <span class="n">buffer</span><span class="p">;</span> 

    <span class="c1">// 使用 C++17 结构化绑定</span>
    <span class="k">if</span> <span class="p">(</span><span class="k">auto</span> <span class="p">[</span><span class="n">ptr</span><span class="p">,</span> <span class="n">ec</span><span class="p">]</span> <span class="o">=</span> <span class="n">std</span><span class="o">::</span><span class="n">to_chars</span><span class="p">(</span><span class="n">buffer</span><span class="p">.</span><span class="n">data</span><span class="p">(),</span> <span class="n">buffer</span><span class="p">.</span><span class="n">data</span><span class="p">()</span> <span class="o">+</span> <span class="n">buffer</span><span class="p">.</span><span class="n">size</span><span class="p">(),</span> <span class="n">my_value</span><span class="p">,</span> <span class="mi">10</span><span class="p">);</span>
        <span class="n">ec</span> <span class="o">==</span> <span class="n">std</span><span class="o">::</span><span class="n">errc</span><span class="p">())</span> 
    <span class="p">{</span>
        <span class="c1">// 成功！</span>
        <span class="c1">// ptr 指向写入的末尾</span>
        <span class="c1">// 使用 string_view 来安全地封装结果, 无需拷贝</span>
        <span class="n">std</span><span class="o">::</span><span class="n">string_view</span> <span class="n">sv</span><span class="p">(</span><span class="n">buffer</span><span class="p">.</span><span class="n">data</span><span class="p">(),</span> <span class="n">ptr</span> <span class="o">-</span> <span class="n">buffer</span><span class="p">.</span><span class="n">data</span><span class="p">());</span>
        
        <span class="n">std</span><span class="o">::</span><span class="n">cout</span> <span class="o">&lt;&lt;</span> <span class="s">"Value: "</span> <span class="o">&lt;&lt;</span> <span class="n">my_value</span> <span class="o">&lt;&lt;</span> <span class="n">std</span><span class="o">::</span><span class="n">endl</span><span class="p">;</span>
        <span class="n">std</span><span class="o">::</span><span class="n">cout</span> <span class="o">&lt;&lt;</span> <span class="s">"String: '"</span> <span class="o">&lt;&lt;</span> <span class="n">sv</span> <span class="o">&lt;&lt;</span> <span class="s">"'"</span> <span class="o">&lt;&lt;</span> <span class="n">std</span><span class="o">::</span><span class="n">endl</span><span class="p">;</span>
        <span class="n">std</span><span class="o">::</span><span class="n">cout</span> <span class="o">&lt;&lt;</span> <span class="s">"Length: "</span> <span class="o">&lt;&lt;</span> <span class="n">sv</span><span class="p">.</span><span class="n">length</span><span class="p">()</span> <span class="o">&lt;&lt;</span> <span class="n">std</span><span class="o">::</span><span class="n">endl</span><span class="p">;</span>
    <span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
        <span class="c1">// 缓冲区太小 (value_too_large) 或其他错误</span>
        <span class="n">std</span><span class="o">::</span><span class="n">cout</span> <span class="o">&lt;&lt;</span> <span class="s">"Conversion failed: "</span> <span class="o">&lt;&lt;</span> <span class="n">error_to_string</span><span class="p">(</span><span class="n">ec</span><span class="p">)</span> <span class="o">&lt;&lt;</span> <span class="n">std</span><span class="o">::</span><span class="n">endl</span><span class="p">;</span>
    <span class="p">}</span>

    <span class="c1">// ------------------------------------------------</span>
    <span class="c1">// 示例 2: std::from_chars (string -&gt; int)</span>
    <span class="c1">// ------------------------------------------------</span>
    <span class="n">std</span><span class="o">::</span><span class="n">cout</span> <span class="o">&lt;&lt;</span> <span class="s">"</span><span class="se">\n</span><span class="s">--- std::from_chars ---"</span> <span class="o">&lt;&lt;</span> <span class="n">std</span><span class="o">::</span><span class="n">endl</span><span class="p">;</span>
    <span class="n">std</span><span class="o">::</span><span class="n">string_view</span> <span class="n">str_valid</span> <span class="o">=</span> <span class="s">"42 Hello"</span><span class="p">;</span>
    <span class="n">std</span><span class="o">::</span><span class="n">string_view</span> <span class="n">str_invalid</span> <span class="o">=</span> <span class="s">"NotANumber"</span><span class="p">;</span>
    <span class="n">std</span><span class="o">::</span><span class="n">string_view</span> <span class="n">str_overflow</span> <span class="o">=</span> <span class="s">"99999999999999999999999999999"</span><span class="p">;</span>
    <span class="kt">int</span> <span class="n">result</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span>

    <span class="c1">// 尝试解析 "42 Hello"</span>
    <span class="k">if</span> <span class="p">(</span><span class="k">auto</span> <span class="p">[</span><span class="n">ptr</span><span class="p">,</span> <span class="n">ec</span><span class="p">]</span> <span class="o">=</span> <span class="n">std</span><span class="o">::</span><span class="n">from_chars</span><span class="p">(</span><span class="n">str_valid</span><span class="p">.</span><span class="n">data</span><span class="p">(),</span> <span class="n">str_valid</span><span class="p">.</span><span class="n">data</span><span class="p">()</span> <span class="o">+</span> <span class="n">str_valid</span><span class="p">.</span><span class="n">size</span><span class="p">(),</span> <span class="n">result</span><span class="p">,</span> <span class="mi">10</span><span class="p">);</span>
        <span class="n">ec</span> <span class="o">==</span> <span class="n">std</span><span class="o">::</span><span class="n">errc</span><span class="p">())</span>
    <span class="p">{</span>
        <span class="n">std</span><span class="o">::</span><span class="n">cout</span> <span class="o">&lt;&lt;</span> <span class="s">"Parsed '"</span> <span class="o">&lt;&lt;</span> <span class="n">str_valid</span> <span class="o">&lt;&lt;</span> <span class="s">"' -&gt; "</span> <span class="o">&lt;&lt;</span> <span class="n">result</span> <span class="o">&lt;&lt;</span> <span class="n">std</span><span class="o">::</span><span class="n">endl</span><span class="p">;</span>
        <span class="n">std</span><span class="o">::</span><span class="n">cout</span> <span class="o">&lt;&lt;</span> <span class="s">"Remaining string starts at: '"</span> <span class="o">&lt;&lt;</span> <span class="n">ptr</span> <span class="o">&lt;&lt;</span> <span class="s">"'"</span> <span class="o">&lt;&lt;</span> <span class="n">std</span><span class="o">::</span><span class="n">endl</span><span class="p">;</span> <span class="c1">// 应该指向 ' Hello'</span>
    <span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
        <span class="n">std</span><span class="o">::</span><span class="n">cout</span> <span class="o">&lt;&lt;</span> <span class="s">"Failed to parse '"</span> <span class="o">&lt;&lt;</span> <span class="n">str_valid</span> <span class="o">&lt;&lt;</span> <span class="s">"': "</span> <span class="o">&lt;&lt;</span> <span class="n">error_to_string</span><span class="p">(</span><span class="n">ec</span><span class="p">)</span> <span class="o">&lt;&lt;</span> <span class="n">std</span><span class="o">::</span><span class="n">endl</span><span class="p">;</span>
    <span class="p">}</span>

    <span class="c1">// 尝试解析 "NotANumber"</span>
    <span class="k">if</span> <span class="p">(</span><span class="k">auto</span> <span class="p">[</span><span class="n">ptr</span><span class="p">,</span> <span class="n">ec</span><span class="p">]</span> <span class="o">=</span> <span class="n">std</span><span class="o">::</span><span class="n">from_chars</span><span class="p">(</span><span class="n">str_invalid</span><span class="p">.</span><span class="n">data</span><span class="p">(),</span> <span class="n">str_invalid</span><span class="p">.</span><span class="n">data</span><span class="p">()</span> <span class="o">+</span> <span class="n">str_invalid</span><span class="p">.</span><span class="n">size</span><span class="p">(),</span> <span class="n">result</span><span class="p">,</span> <span class="mi">10</span><span class="p">);</span>
        <span class="n">ec</span> <span class="o">==</span> <span class="n">std</span><span class="o">::</span><span class="n">errc</span><span class="p">())</span>
    <span class="p">{</span>
        <span class="n">std</span><span class="o">::</span><span class="n">cout</span> <span class="o">&lt;&lt;</span> <span class="s">"Parsed '"</span> <span class="o">&lt;&lt;</span> <span class="n">str_invalid</span> <span class="o">&lt;&lt;</span> <span class="s">"' -&gt; "</span> <span class="o">&lt;&lt;</span> <span class="n">result</span> <span class="o">&lt;&lt;</span> <span class="n">std</span><span class="o">::</span><span class="n">endl</span><span class="p">;</span>
    <span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
        <span class="c1">// 这将失败, ec == std::errc::invalid_argument</span>
        <span class="n">std</span><span class="o">::</span><span class="n">cout</span> <span class="o">&lt;&lt;</span> <span class="s">"Failed to parse '"</span> <span class="o">&lt;&lt;</span> <span class="n">str_invalid</span> <span class="o">&lt;&lt;</span> <span class="s">"': "</span> <span class="o">&lt;&lt;</span> <span class="n">error_to_string</span><span class="p">(</span><span class="n">ec</span><span class="p">)</span> <span class="o">&lt;&lt;</span> <span class="n">std</span><span class="o">::</span><span class="n">endl</span><span class="p">;</span>
    <span class="p">}</span>

    <span class="c1">// 尝试解析溢出的数字</span>
    <span class="k">if</span> <span class="p">(</span><span class="k">auto</span> <span class="p">[</span><span class="n">ptr</span><span class="p">,</span> <span class="n">ec</span><span class="p">]</span> <span class="o">=</span> <span class="n">std</span><span class="o">::</span><span class="n">from_chars</span><span class="p">(</span><span class="n">str_overflow</span><span class="p">.</span><span class="n">data</span><span class="p">(),</span> <span class="n">str_overflow</span><span class="p">.</span><span class="n">data</span><span class="p">()</span> <span class="o">+</span> <span class="n">str_overflow</span><span class="p">.</span><span class="n">size</span><span class="p">(),</span> <span class="n">result</span><span class="p">,</span> <span class="mi">10</span><span class="p">);</span>
        <span class="n">ec</span> <span class="o">==</span> <span class="n">std</span><span class="o">::</span><span class="n">errc</span><span class="p">())</span>
    <span class="p">{</span>
        <span class="n">std</span><span class="o">::</span><span class="n">cout</span> <span class="o">&lt;&lt;</span> <span class="s">"Parsed '"</span> <span class="o">&lt;&lt;</span> <span class="n">str_overflow</span> <span class="o">&lt;&lt;</span> <span class="s">"' -&gt; "</span> <span class="o">&lt;&lt;</span> <span class="n">result</span> <span class="o">&lt;&lt;</span> <span class="n">std</span><span class="o">::</span><span class="n">endl</span><span class="p">;</span>
    <span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
        <span class="c1">// 这将失败, ec == std::errc::result_out_of_range</span>
        <span class="n">std</span><span class="o">::</span><span class="n">cout</span> <span class="o">&lt;&lt;</span> <span class="s">"Failed to parse '"</span> <span class="o">&lt;&lt;</span> <span class="n">str_overflow</span> <span class="o">&lt;&lt;</span> <span class="s">"': "</span> <span class="o">&lt;&lt;</span> <span class="n">error_to_string</span><span class="p">(</span><span class="n">ec</span><span class="p">)</span> <span class="o">&lt;&lt;</span> <span class="n">std</span><span class="o">::</span><span class="n">endl</span><span class="p">;</span>
    <span class="p">}</span>
    
    <span class="k">return</span> <span class="mi">0</span><span class="p">;</span>
<span class="p">}</span>
</code></pre></div></div>

<p>输出如下:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>--- std::to_chars ---
Value: 1989
String: '1989'
Length: 4

--- std::from_chars ---
Parsed '42 Hello' -&gt; 42
Remaining string starts at: ' Hello'
Failed to parse 'NotANumber': Invalid argument
Failed to parse '99999999999999999999999999999': Result too large
</code></pre></div></div>

<h3 id="总结">总结</h3>

<p><code class="language-plaintext highlighter-rouge">&lt;charconv&gt;</code> 是 C++17 中一项革命性的补充. 当你需要进行<strong>高性能</strong>的数字和字符串转换时, 它应该是你的<strong>首选</strong>, 特别是在以下场景:</p>

<ul>
  <li>解析 JSON, XML 或自定义的配置文件.</li>
  <li>网络编程中序列化/反序列化消息.</li>
  <li>游戏引擎或实时模拟.</li>
  <li>任何你希望避免异常和内存分配的底层库代码.</li>
</ul>]]></content><author><name>Mikami</name></author><category term="cpp" /><category term="cpp" /><category term="code" /><summary type="html"><![CDATA[&lt;charconv&gt; 是 C++17 标准库中引入的一个非常重要且实用的头文件. 它的核心目标是提供高性能, 无异常, 无内存分配, 且区域设置(locale)无关的基本数值类型与字符串之间的相互转换.]]></summary></entry><entry><title type="html">C++非静态成员函数指针原理总结</title><link href="https://mikami-w.github.io/cpp/2025/10/09/C++%E6%88%90%E5%91%98%E5%87%BD%E6%95%B0%E6%8C%87%E9%92%88%E5%8E%9F%E7%90%86%E6%80%BB%E7%BB%93.html" rel="alternate" type="text/html" title="C++非静态成员函数指针原理总结" /><published>2025-10-09T00:00:00+00:00</published><updated>2025-10-09T00:00:00+00:00</updated><id>https://mikami-w.github.io/cpp/2025/10/09/C++%E6%88%90%E5%91%98%E5%87%BD%E6%95%B0%E6%8C%87%E9%92%88%E5%8E%9F%E7%90%86%E6%80%BB%E7%BB%93</id><content type="html" xml:base="https://mikami-w.github.io/cpp/2025/10/09/C++%E6%88%90%E5%91%98%E5%87%BD%E6%95%B0%E6%8C%87%E9%92%88%E5%8E%9F%E7%90%86%E6%80%BB%E7%BB%93.html"><![CDATA[<p>C++的成员函数指针是一种强大但晦涩的工具. 与普通函数指针不同, 它不能被直接调用, 而必须通过 <code class="language-plaintext highlighter-rouge">.*</code> 或 <code class="language-plaintext highlighter-rouge">-&gt;*</code> 运算符与一个对象实例绑定. 这个看似简单的调用语法 <code class="language-plaintext highlighter-rouge">(object.*pointer)()</code>, 背后却隐藏着一套由编译器和C++对象模型共同协作的、精密的<code class="language-plaintext highlighter-rouge">this</code>指针调整机制, 尤其是在处理多重继承时.</p>

<h2 id="成员函数指针的使用">成员函数指针的使用</h2>

<h4 id="1-问题的根源-多重继承与this指针">1. 问题的根源: 多重继承与<code class="language-plaintext highlighter-rouge">this</code>指针</h4>

<p>要理解为何需要调整, 首先要看多重继承的内存布局. 假设有如下结构:</p>

<div class="language-cpp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">struct</span> <span class="nc">Base1</span> <span class="p">{</span> <span class="kt">int</span> <span class="n">b1</span><span class="p">;</span> <span class="p">};</span>
<span class="k">struct</span> <span class="nc">Base2</span> <span class="p">{</span> <span class="kt">int</span> <span class="n">b2</span><span class="p">;</span> <span class="p">};</span>
<span class="k">struct</span> <span class="nc">Derived</span> <span class="o">:</span> <span class="k">public</span> <span class="n">Base1</span><span class="p">,</span> <span class="k">public</span> <span class="n">Base2</span> <span class="p">{</span> <span class="kt">int</span> <span class="n">d</span><span class="p">;</span> <span class="p">};</span>
</code></pre></div></div>

<p>一个 <code class="language-plaintext highlighter-rouge">Derived</code> 对象的内存布局通常是 <code class="language-plaintext highlighter-rouge">[ Base1 subobject | Base2 subobject | Derived members ]</code>.  这意味着, 一个 <code class="language-plaintext highlighter-rouge">Derived</code> 对象指针 <code class="language-plaintext highlighter-rouge">d_ptr</code> 在数值上与其 <code class="language-plaintext highlighter-rouge">Base1</code> 子对象的指针相等, 但与 <code class="language-plaintext highlighter-rouge">Base2</code> 子对象的指针不相等.</p>

<div class="language-cpp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">Derived</span><span class="o">*</span> <span class="n">d_ptr</span> <span class="o">=</span> <span class="k">new</span> <span class="nf">Derived</span><span class="p">();</span>
<span class="n">Base1</span><span class="o">*</span> <span class="n">b1_ptr</span> <span class="o">=</span> <span class="n">d_ptr</span><span class="p">;</span> <span class="c1">// 地址值相同</span>
<span class="n">Base2</span><span class="o">*</span> <span class="n">b2_ptr</span> <span class="o">=</span> <span class="n">d_ptr</span><span class="p">;</span> <span class="c1">// 地址值不同！编译器会自动加上一个偏移量</span>
</code></pre></div></div>

<p>因此, 如果一个成员函数指针指向 <code class="language-plaintext highlighter-rouge">Base2</code> 的成员, 并通过一个 <code class="language-plaintext highlighter-rouge">Derived</code> 对象来调用它, 那么传递给该函数的 <code class="language-plaintext highlighter-rouge">this</code> 指针必须被精确地调整到 <code class="language-plaintext highlighter-rouge">Base2</code> 子对象的起始地址.</p>

<h4 id="2-成员函数指针的用法-usage">2. 成员函数指针的用法 (Usage)</h4>

<p>成员函数指针是一种独特的C++类型, 用于存储一个非静态成员函数的“调用信息”. 其使用遵循三个步骤:</p>

<ol>
  <li>
    <p><strong>声明 (Declaration)</strong> 其类型包含函数的返回类型、所属类名和参数列表.  <code class="language-plaintext highlighter-rouge">ReturnType (ClassName::*PointerName)(ArgTypes);</code></p>

    <div class="language-cpp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// 声明一个指针 p_func, 它可以指向 MyClass 中任何“返回void, 无参数”的成员函数</span>
<span class="kt">void</span> <span class="p">(</span><span class="n">MyClass</span><span class="o">::*</span><span class="n">p_func</span><span class="p">)();</span>
</code></pre></div>    </div>
  </li>
  <li>
    <p><strong>赋值 (Assignment)</strong> 必须使用取地址运算符 <code class="language-plaintext highlighter-rouge">&amp;</code>, 并明确指定类作用域.  <code class="language-plaintext highlighter-rouge">PointerName = &amp;ClassName::FunctionName;</code></p>

    <div class="language-cpp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">auto</span> <span class="n">p_func</span> <span class="o">=</span> <span class="o">&amp;</span><span class="n">MyClass</span><span class="o">::</span><span class="n">my_method</span><span class="p">;</span>
<span class="c1">// 展开后的完整类型声明如下:</span>
<span class="c1">// void (MyClass::*p_func) = &amp;MyClass::my_method;</span>
</code></pre></div>    </div>
  </li>
  <li>
    <p><strong>调用 (Invocation)</strong> 必须通过 <code class="language-plaintext highlighter-rouge">.*</code> (用于对象或引用) 或 <code class="language-plaintext highlighter-rouge">-&gt;*</code> (用于指针) 运算符, 将其与一个类的实例“绑定”后才能调用.</p>

    <div class="language-cpp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">MyClass</span> <span class="n">obj</span><span class="p">;</span>
<span class="n">MyClass</span><span class="o">*</span> <span class="n">p_obj</span> <span class="o">=</span> <span class="o">&amp;</span><span class="n">obj</span><span class="p">;</span>
   
<span class="p">(</span><span class="n">obj</span><span class="p">.</span><span class="o">*</span><span class="n">p_func</span><span class="p">)();</span>      <span class="c1">// 通过对象调用</span>
<span class="p">(</span><span class="n">p_obj</span><span class="o">-&gt;*</span><span class="n">p_func</span><span class="p">)();</span>   <span class="c1">// 通过对象指针调用</span>
</code></pre></div>    </div>
  </li>
</ol>

<p><strong>现代C++ (C++11及以后) 更倾向于使用<code class="language-plaintext highlighter-rouge">std::function</code>和 Lambda 表达式而非裸露的成员函数指针作为通用的回调机制.</strong></p>

<h2 id="成员函数指针的原理">成员函数指针的原理</h2>

<h4 id="3-实际的-ptr-adj-结构">3. 实际的 <code class="language-plaintext highlighter-rouge">{ptr, adj}</code> 结构</h4>

<p>在遵循 <strong>Itanium C++ ABI</strong> 的64位系统 (如 g++ on Linux) 上, 一个成员函数指针的内存表示为一个16字节的结构体, 我们称其为 <code class="language-plaintext highlighter-rouge">{ptr, adj}</code> 组合.</p>

<div class="language-cpp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">struct</span> <span class="nc">MemberFunctionPointer</span> <span class="p">{</span>
    <span class="c1">// 函数信息: 对于非虚函数是地址, 对于虚函数是vtable偏移</span>
    <span class="kt">void</span><span class="o">*</span> <span class="n">ptr</span><span class="p">;</span>
    <span class="c1">// this指针调整量 (字节偏移) </span>
    <span class="n">std</span><span class="o">::</span><span class="kt">ptrdiff_t</span> <span class="n">adj</span><span class="p">;</span> 
<span class="p">};</span>
</code></pre></div></div>

<ul>
  <li><strong><code class="language-plaintext highlighter-rouge">ptr</code></strong>: 存储函数的核心信息.</li>
  <li><strong><code class="language-plaintext highlighter-rouge">adj</code></strong>: 存储一个偏移量, 用于在某些复杂的继承场景下调整<code class="language-plaintext highlighter-rouge">this</code>指针.</li>
</ul>

<p>具体来讲,</p>

<ul>
  <li><strong><code class="language-plaintext highlighter-rouge">ptr</code> (<code class="language-plaintext highlighter-rouge">void*</code>, 8字节)</strong>: <code class="language-plaintext highlighter-rouge">ptr</code> 字段的内容取决于所指向的函数类型:
    <ol>
      <li><strong>指向非虚函数 (Non-Virtual Function)</strong>
        <ul>
          <li><code class="language-plaintext highlighter-rouge">ptr</code> 存储该函数在代码段中的<strong>直接内存地址</strong>.</li>
          <li>根据ABI假设, 这个地址的<strong>最低位永远是0</strong>.</li>
        </ul>
      </li>
      <li><strong>指向虚函数 (Virtual Function)</strong>
        <ul>
          <li><code class="language-plaintext highlighter-rouge">ptr</code> 存储一个<strong>编码后的值</strong>: <code class="language-plaintext highlighter-rouge">1 + vtable_offset</code>.</li>
          <li><strong>最低位为1</strong>: 这是虚函数的“身份标志”.</li>
          <li><strong>值右移一位 (<code class="language-plaintext highlighter-rouge">ptr &gt;&gt; 1</code>)</strong>: 得到的是该函数在其v-table中的<strong>字节偏移量</strong>. 例如, 如果 <code class="language-plaintext highlighter-rouge">print_v</code> 是虚析构函数后的第一个虚函数, 其vtable偏移为8, 则 <code class="language-plaintext highlighter-rouge">ptr</code> 的值会是 <code class="language-plaintext highlighter-rouge">1 + 8 = 9</code> (二进制 <code class="language-plaintext highlighter-rouge">...1001</code>).</li>
        </ul>
      </li>
      <li><strong>指向 <code class="language-plaintext highlighter-rouge">nullptr</code></strong>
        <ul>
          <li><code class="language-plaintext highlighter-rouge">ptr</code> 字段的值为 <code class="language-plaintext highlighter-rouge">0</code> (<code class="language-plaintext highlighter-rouge">NULL</code>).</li>
        </ul>
      </li>
    </ol>
  </li>
  <li><strong><code class="language-plaintext highlighter-rouge">adj</code> (<code class="language-plaintext highlighter-rouge">ptrdiff_t</code>, 8字节)</strong>:
    <ul>
      <li>产生于成员函数指针赋值时, 赋值操作 <code class="language-plaintext highlighter-rouge">pfunc_derived = &amp;base::func</code> 本质是一次<strong>从“指向基类成员的指针”到“指向派生类成员的指针”的类型转换</strong>.</li>
      <li>在进行这个转换时, 编译器会<strong>预先计算</strong>出基类子对象 (<code class="language-plaintext highlighter-rouge">base</code>) 在派生类 (<code class="language-plaintext highlighter-rouge">derived</code>) 中的内存偏移量, 即<code class="language-plaintext highlighter-rouge">adj</code>. 如果该基类是第一个基类, 则偏移量<code class="language-plaintext highlighter-rouge">adj</code>为0.</li>
      <li><strong>但在可观测结果相同的前提下, 实际情况取决于编译器实现, 因为C++标准中未给出具体实现, 以上说法均根据 Itanium C++ ABI 与实验观测结果.</strong></li>
    </ul>
  </li>
</ul>

<h4 id="4--运算符的统一调用模型">4. <code class="language-plaintext highlighter-rouge">.*</code> 运算符的统一调用模型</h4>

<p>当编译器遇到 <code class="language-plaintext highlighter-rouge">(object.*pointer)()</code> 这样的表达式时, 它会遵循一个统一的两步模型来确保<code class="language-plaintext highlighter-rouge">this</code>指针的正确性.</p>

<p><strong>第一步: 编译期类型对齐 (Compile-Time Type Alignment)</strong></p>

<p>编译器首先比较 <code class="language-plaintext highlighter-rouge">object</code> 的类型和 <code class="language-plaintext highlighter-rouge">pointer</code> 所属的类类型.</p>

<p>如果 <code class="language-plaintext highlighter-rouge">object</code> 的类型是 <code class="language-plaintext highlighter-rouge">pointer</code> 所属类的<strong>派生类</strong>, 编译器会<strong>在编译期</strong>执行一次<strong>向上转型 (Upcasting)</strong>. 这会生成在运行时调整地址的指令, 将 <code class="language-plaintext highlighter-rouge">object</code> 的地址转换为其基类子对象的地址.</p>

<ul>
  <li><strong>示例</strong>: <code class="language-plaintext highlighter-rouge">(derived_obj.*base_class_pointer)()</code></li>
  <li><strong>动作</strong>: 编译器生成代码, 将 <code class="language-plaintext highlighter-rouge">&amp;derived_obj</code> 的地址调整为 <code class="language-plaintext highlighter-rouge">derived_obj</code> 内部 <code class="language-plaintext highlighter-rouge">Base</code> 子对象的地址.</li>
</ul>

<p>如果类型匹配, 则此步骤不产生地址变化.</p>

<p><strong>第二步: <code class="language-plaintext highlighter-rouge">adj</code> 偏移应用 (Runtime <code class="language-plaintext highlighter-rouge">adj</code> Adjustment)</strong></p>

<p>在第一步 (如果需要的话) 完成地址调整之后, 程序会读取成员函数指针内部的 <code class="language-plaintext highlighter-rouge">adj</code> 字段, 并将其加到<strong>第一步调整后</strong>的地址上.</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>final_this = result_from_step_1 + pointer.adj;
</code></pre></div></div>

<p>这一步解释了为何成员函数指针需要 <code class="language-plaintext highlighter-rouge">adj</code> 字段. 当一个“指向基类成员的指针”被赋值给一个“指向派生类成员的指针”变量时, <code class="language-plaintext highlighter-rouge">adj</code> 就会被编译器设置为一个非零值, 用以表示这两个类之间的内存偏移.</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>derived class:
+-------------------+
| |     base1     | | -&gt; adj = 0
| +---------------+ |
| |     base2     | | -&gt; adj = sizeof(base1)
| +---------------+ |
| | other members | |
+-------------------+
</code></pre></div></div>

<p>第一步类型对齐和第二步<code class="language-plaintext highlighter-rouge">adj</code>偏移对this指针调整的应用发生在<code class="language-plaintext highlighter-rouge">.*</code>运算符的调用时, 而<code class="language-plaintext highlighter-rouge">adj</code>的生成发生在成员函数指针的赋值时.</p>

<hr />

<h4 id="与普通函数指针的差异及问题">与普通函数指针的差异及问题</h4>

<p>成员函数指针与普通函数指针在原理上完全不同, 不可混用.</p>

<table>
  <thead>
    <tr>
      <th>对比项</th>
      <th>普通函数指针</th>
      <th>非静态成员函数指针</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td><strong>核心原理</strong></td>
      <td>存储一个<strong>独立函数</strong>的内存地址. 它是一个完整的调用目标.</td>
      <td>存储一个<strong>依赖于对象</strong>的函数的调用信息. 它是一个不完整的“配方”, 需要对象实例才能调用.</td>
    </tr>
    <tr>
      <td><strong><code class="language-plaintext highlighter-rouge">this</code> 指针</strong></td>
      <td>不需要</td>
      <td><strong>需要</strong>. 这是其存在的核心, 也是一切复杂性的根源.</td>
    </tr>
    <tr>
      <td><strong>类型系统</strong></td>
      <td><code class="language-plaintext highlighter-rouge">ReturnType (*)(Args)</code></td>
      <td><code class="language-plaintext highlighter-rouge">ReturnType (ClassName::*)(Args)</code>. 两者类型不兼容.</td>
    </tr>
    <tr>
      <td><strong>大小 (<code class="language-plaintext highlighter-rouge">sizeof</code>)</strong></td>
      <td>等于一个普通指针 (64位下为8字节) .</td>
      <td>通常大于一个普通指针 (64位下为16字节) , 以存储额外信息.</td>
    </tr>
    <tr>
      <td><strong>带来的问题</strong></td>
      <td>无法直接存储成员函数.</td>
      <td>不能存入普通函数指针数组, 不能直接转换为<code class="language-plaintext highlighter-rouge">void*</code>(这使得试图使用<code class="language-plaintext highlighter-rouge">std::cout</code>输出成员函数指针时重载决议不得不将其转换为<code class="language-plaintext highlighter-rouge">bool</code>型). 需要使用<code class="language-plaintext highlighter-rouge">std::function</code>等类型擦除工具才能与普通函数统一处理.</td>
    </tr>
  </tbody>
</table>]]></content><author><name>Mikami</name></author><category term="cpp" /><category term="cpp" /><category term="code" /><summary type="html"><![CDATA[C++的成员函数指针是一种强大但晦涩的工具. 与普通函数指针不同, 它不能被直接调用, 而必须通过 .* 或 -&gt;* 运算符与一个对象实例绑定. 这个看似简单的调用语法 (object.*pointer)(), 背后却隐藏着一套由编译器和C++对象模型共同协作的、精密的this指针调整机制, 尤其是在处理多重继承时.]]></summary></entry><entry><title type="html">.git目录中路径问题与本地仓库迁移</title><link href="https://mikami-w.github.io/git/2025/10/08/.git%E7%9B%AE%E5%BD%95%E4%B8%AD%E8%B7%AF%E5%BE%84%E9%97%AE%E9%A2%98%E4%B8%8E%E6%9C%AC%E5%9C%B0%E4%BB%93%E5%BA%93%E8%BF%81%E7%A7%BB.html" rel="alternate" type="text/html" title=".git目录中路径问题与本地仓库迁移" /><published>2025-10-08T00:00:00+00:00</published><updated>2025-10-08T00:00:00+00:00</updated><id>https://mikami-w.github.io/git/2025/10/08/.git%E7%9B%AE%E5%BD%95%E4%B8%AD%E8%B7%AF%E5%BE%84%E9%97%AE%E9%A2%98%E4%B8%8E%E6%9C%AC%E5%9C%B0%E4%BB%93%E5%BA%93%E8%BF%81%E7%A7%BB</id><content type="html" xml:base="https://mikami-w.github.io/git/2025/10/08/.git%E7%9B%AE%E5%BD%95%E4%B8%AD%E8%B7%AF%E5%BE%84%E9%97%AE%E9%A2%98%E4%B8%8E%E6%9C%AC%E5%9C%B0%E4%BB%93%E5%BA%93%E8%BF%81%E7%A7%BB.html"><![CDATA[<h3 id="1-git-目录中是否有本地路径相关的信息">1. <code class="language-plaintext highlighter-rouge">.git</code> 目录中是否有本地路径相关的信息?</h3>

<p>核心答案是：<strong>几乎没有, 而且设计上就是为了避免有.</strong></p>

<p>Git 的一个核心设计理念就是<strong>可移植性 (Portability)</strong>. 一个 Git 仓库被设计成一个完全自包含的单元, 你可以把它复制到任何地方（另一台电脑、一个 U 盘、一个网络驱动器）, 它都能正常工作.</p>

<p>如果 <code class="language-plaintext highlighter-rouge">.git</code> 目录中硬编码了你本地的绝对路径（比如 <code class="language-plaintext highlighter-rouge">C:\Users\MyUser\Projects\my-repo</code> 或 <code class="language-plaintext highlighter-rouge">/home/user/works/my-repo</code>）, 那么一旦你移动了它, 或者把它发给同事, 仓库就会立刻“损坏”.</p>

<p>那么 <code class="language-plaintext highlighter-rouge">.git</code> 目录里到底存了什么呢?</p>

<ul>
  <li>
    <p><strong>对象 (objects)</strong>: 包含了你所有的提交、文件内容和目录结构. 这些都是通过哈希值组织的, 与文件系统的路径无关.</p>
  </li>
  <li>
    <p><strong>引用 (refs)</strong>: 指向各个分支和标签的指针（比如 <code class="language-plaintext highlighter-rouge">refs/heads/main</code>）.</p>
  </li>
  <li>
    <p><strong>HEAD</strong>: 指向你当前所在的分支.</p>
  </li>
  <li>
    <p><strong>config</strong>: 这是唯一一个可能包含“路径”信息的文件, 但它包含的是<strong>远程仓库的 URL 地址</strong>, 而不是你本地仓库的路径.</p>

    <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>[remote "origin"]
    url = https://github.com/your-username/your-repo.git
    fetch = +refs/heads/*:refs/remotes/origin/*
</code></pre></div>    </div>

    <p>这个 URL 指向的是 GitHub 服务器, 而不是你本地的 <code class="language-plaintext highlighter-rouge">D:</code> 盘或 <code class="language-plaintext highlighter-rouge">/home</code> 目录.</p>
  </li>
</ul>

<p><strong>唯一的例外情况</strong>： 如果你自己编写了<strong>自定义的 Git 钩子 (Git Hooks)</strong>（位于 <code class="language-plaintext highlighter-rouge">.git/hooks/</code> 目录下的脚本）, 并且在脚本中<strong>硬编码了绝对路径</strong>, 那么这些脚本里就会包含本地路径信息. 但这是用户自己的行为, 并非 Git 的默认机制.</p>

<hr />

<h3 id="2-能否通过直接移动整个仓库文件夹的方式实现转移">2. 能否通过直接移动整个仓库文件夹的方式实现转移?</h3>

<p>答案是：<strong>完全可以, 而且这正是推荐的做法.</strong></p>

<p>基于上面的解释, 因为 Git 仓库是自包含的, 所以移动它的位置就和移动一个普通的文件夹一样简单和安全.</p>

<p>当你移动了整个项目文件夹（例如从 <code class="language-plaintext highlighter-rouge">C:\projects</code> 移动到 <code class="language-plaintext highlighter-rouge">D:\my-work</code>）, 包含了工作区文件和 <code class="language-plaintext highlighter-rouge">.git</code> 目录的所有相对关系都没有改变.</p>

<p><strong>操作流程非常简单：</strong></p>

<ol>
  <li><strong>关闭相关程序</strong>：确保没有正在使用该仓库的程序在运行（比如 VS Code、Sourcetree、终端等）, 以避免文件占用问题.</li>
  <li><strong>移动文件夹</strong>：直接通过你的文件资源管理器（或命令行中的 <code class="language-plaintext highlighter-rouge">mv</code> 命令）剪切并粘贴整个项目文件夹到新的位置.</li>
  <li><strong>在新位置继续工作</strong>：
    <ul>
      <li>打开一个新的终端窗口.</li>
      <li><code class="language-plaintext highlighter-rouge">cd</code> 到<strong>新的目录路径</strong>下.</li>
      <li>运行 <code class="language-plaintext highlighter-rouge">git status</code> 或 <code class="language-plaintext highlighter-rouge">git log</code>.</li>
    </ul>
  </li>
</ol>

<p>你会发现, 所有 Git 命令都像以前一样正常工作. Git 会自动在当前目录及其父目录中寻找 <code class="language-plaintext highlighter-rouge">.git</code> 文件夹来确定自己所处的仓库, 这个机制与文件夹的绝对路径无关.</p>

<h4 id="需要注意的潜在影响-git-本身之外">需要注意的潜在影响 (Git 本身之外)</h4>

<p>虽然移动对 Git 仓库本身没有影响, 但可能会影响到你的<strong>工作环境</strong>：</p>

<ul>
  <li><strong>IDE / 编辑器</strong>: 像 VS Code、IntelliJ IDEA 或 Sourcetree 这样的工具, 它们的“最近打开的项目”列表里记录的是旧的绝对路径. 移动文件夹后, 你需要从新位置重新“打开项目”, 此后这个快捷方式就会更新.</li>
  <li><strong>终端的书签或历史记录</strong>: 如果你使用了终端书签或者依赖命令历史记录来进入项目目录, 你需要更新它们.</li>
  <li><strong>自定义脚本</strong>: 如果你有任何外部脚本（非 Git Hooks）依赖于项目的旧路径, 你需要手动更新这些脚本.</li>
</ul>]]></content><author><name>Mikami</name></author><category term="git" /><category term="git" /><summary type="html"><![CDATA[1. .git 目录中是否有本地路径相关的信息?]]></summary></entry><entry><title type="html">C++数组大小与数组退化详解</title><link href="https://mikami-w.github.io/cpp/2025/10/08/C++%E6%95%B0%E7%BB%84%E5%A4%A7%E5%B0%8F,%E6%95%B0%E7%BB%84%E9%80%80%E5%8C%96%E4%B8%8E%E6%95%B0%E7%BB%84%E5%BC%95%E7%94%A8%E8%AF%A6%E8%A7%A3.html" rel="alternate" type="text/html" title="C++数组大小与数组退化详解" /><published>2025-10-08T00:00:00+00:00</published><updated>2025-10-08T00:00:00+00:00</updated><id>https://mikami-w.github.io/cpp/2025/10/08/C++%E6%95%B0%E7%BB%84%E5%A4%A7%E5%B0%8F,%E6%95%B0%E7%BB%84%E9%80%80%E5%8C%96%E4%B8%8E%E6%95%B0%E7%BB%84%E5%BC%95%E7%94%A8%E8%AF%A6%E8%A7%A3</id><content type="html" xml:base="https://mikami-w.github.io/cpp/2025/10/08/C++%E6%95%B0%E7%BB%84%E5%A4%A7%E5%B0%8F,%E6%95%B0%E7%BB%84%E9%80%80%E5%8C%96%E4%B8%8E%E6%95%B0%E7%BB%84%E5%BC%95%E7%94%A8%E8%AF%A6%E8%A7%A3.html"><![CDATA[<p>在C++中, 将原生数组传递给函数时, 一个常见的问题是在函数内部无法正确获取数组的原始大小.</p>

<p>例如, 以下代码的输出可能不符合直觉:</p>

<div class="language-cpp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="cp">#include</span> <span class="cpf">&lt;iostream&gt;</span><span class="cp">
</span>
<span class="kt">void</span> <span class="nf">analyzeArray</span><span class="p">(</span><span class="kt">int</span> <span class="n">arr</span><span class="p">[</span><span class="mi">10</span><span class="p">])</span> <span class="p">{</span>
    <span class="c1">// 试图在函数内获取大小</span>
    <span class="n">std</span><span class="o">::</span><span class="n">cout</span> <span class="o">&lt;&lt;</span> <span class="s">"函数内 sizeof(arr): "</span> <span class="o">&lt;&lt;</span> <span class="k">sizeof</span><span class="p">(</span><span class="n">arr</span><span class="p">)</span> <span class="o">&lt;&lt;</span> <span class="n">std</span><span class="o">::</span><span class="n">endl</span><span class="p">;</span>
<span class="p">}</span>

<span class="kt">int</span> <span class="n">main</span><span class="p">()</span> <span class="p">{</span>
    <span class="kt">int</span> <span class="n">my_array</span><span class="p">[</span><span class="mi">10</span><span class="p">]</span> <span class="o">=</span> <span class="p">{</span><span class="mi">0</span><span class="p">};</span>
    <span class="c1">// 在定义的作用域内获取大小</span>
    <span class="n">std</span><span class="o">::</span><span class="n">cout</span> <span class="o">&lt;&lt;</span> <span class="s">"函数外 sizeof(my_array): "</span> <span class="o">&lt;&lt;</span> <span class="k">sizeof</span><span class="p">(</span><span class="n">my_array</span><span class="p">)</span> <span class="o">&lt;&lt;</span> <span class="n">std</span><span class="o">::</span><span class="n">endl</span><span class="p">;</span>
    
    <span class="n">analyzeArray</span><span class="p">(</span><span class="n">my_array</span><span class="p">);</span>
    <span class="k">return</span> <span class="mi">0</span><span class="p">;</span>
<span class="p">}</span>
</code></pre></div></div>

<p>在64位系统上, 典型的输出为:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>函数外 sizeof(my_array): 40
函数内 sizeof(arr): 8
</code></pre></div></div>

<p><code class="language-plaintext highlighter-rouge">sizeof(my_array)</code> 正确地返回了数组的总字节数（4字节/int * 10个元素 = 40字节）, 而 <code class="language-plaintext highlighter-rouge">sizeof(arr)</code> 返回的却是指针的大小. 这种现象被称为<strong>“数组退化” (Array Decay)</strong>.</p>

<h3 id="数组退化的原理">数组退化的原理</h3>

<p>根据C++标准, 当数组作为函数参数按值传递时, 其类型会被<strong>自动调整 (adjusted)</strong> 为指向其首元素的指针类型.</p>

<p>因此, <code class="language-plaintext highlighter-rouge">void analyzeArray(int arr[10])</code> 的函数签名在功能上与 <code class="language-plaintext highlighter-rouge">void analyzeArray(int* arr)</code> 完全等价. 方括号中的大小 <code class="language-plaintext highlighter-rouge">10</code> 会被编译器忽略, 函数实际接收的是一个 <code class="language-plaintext highlighter-rouge">int*</code> 指针.</p>

<p>这就是为什么在函数内部 <code class="language-plaintext highlighter-rouge">sizeof(arr)</code> 计算的是指针类型的大小, 而不是原始数组的大小. 这个特性源于C语言, 虽然在某些情况下有用, 但也导致了数组尺寸信息的丢失.</p>

<h3 id="解决方案-使用数组引用防止退化">解决方案: 使用数组引用防止退化</h3>

<p>要解决尺寸信息丢失的问题, 就必须阻止数组退化. 这可以通过将函数参数声明为<strong>数组的引用 (Reference to an Array)</strong> 来实现.</p>

<p>其语法如下:</p>

<div class="language-cpp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">T</span> <span class="p">(</span><span class="o">&amp;</span><span class="n">a</span><span class="p">)[</span><span class="n">N</span><span class="p">]</span>
</code></pre></div></div>

<p>这个声明的含义是: <code class="language-plaintext highlighter-rouge">a</code> 是一个引用, 它引用的对象是一个大小为 <code class="language-plaintext highlighter-rouge">N</code>、元素类型为 <code class="language-plaintext highlighter-rouge">T</code> 的数组.</p>

<p>当以引用的方式传递数组时, 函数接收到的是对原始数组对象本身的绑定, 而不是一个指向其首元素的指针. 因此, 数组的完整类型 <code class="language-plaintext highlighter-rouge">T[N]</code> 得以保留, 编译器就能从中得知其尺寸.</p>

<h3 id="通用实现-结合模板在编译期获取大小">通用实现: 结合模板在编译期获取大小</h3>

<p>为了创建一个可以获取任何类型和大小的数组尺寸的通用函数, 可以将数组引用与函数模板相结合.</p>

<div class="language-cpp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="cp">#include</span> <span class="cpf">&lt;cstddef&gt;</span><span class="c1"> // for std::size_t</span><span class="cp">
</span>
<span class="k">template</span><span class="o">&lt;</span><span class="k">class</span> <span class="nc">T</span><span class="p">,</span> <span class="n">std</span><span class="o">::</span><span class="kt">size_t</span> <span class="n">N</span><span class="p">&gt;</span>
<span class="k">constexpr</span> <span class="n">std</span><span class="o">::</span><span class="kt">size_t</span> <span class="n">getArraySize</span><span class="p">(</span><span class="n">T</span> <span class="p">(</span><span class="o">&amp;</span><span class="n">a</span><span class="p">)[</span><span class="n">N</span><span class="p">])</span> <span class="k">noexcept</span>
<span class="p">{</span>
    <span class="k">return</span> <span class="n">N</span><span class="p">;</span>
<span class="p">}</span>
</code></pre></div></div>

<p>该模板函数的工作机制如下:</p>

<ol>
  <li><strong>模板参数</strong>: 函数模板有两个参数: 类型参数 <code class="language-plaintext highlighter-rouge">T</code> 用于匹配数组的元素类型, 非类型模板参数 <code class="language-plaintext highlighter-rouge">std::size_t N</code> 用于匹配数组的大小.</li>
  <li><strong>函数参数</strong>: <code class="language-plaintext highlighter-rouge">T (&amp;a)[N]</code> 确保了只有真正的数组才能作为实参, 并且在传参时不发生退化.</li>
  <li><strong>模板参数推导</strong>: 当一个具体数组被传入时, 编译器会进行参数推导. 例如, 对于一个 <code class="language-plaintext highlighter-rouge">int numbers[10]</code> 类型的数组, 编译器会将其类型 <code class="language-plaintext highlighter-rouge">int[10]</code> 与 <code class="language-plaintext highlighter-rouge">T (&amp;a)[N]</code> 进行匹配, 从而成功推导出 <code class="language-plaintext highlighter-rouge">T</code> 为 <code class="language-plaintext highlighter-rouge">int</code>, <code class="language-plaintext highlighter-rouge">N</code> 为 <code class="language-plaintext highlighter-rouge">10</code>.</li>
  <li><strong>编译期计算</strong>: 函数返回推导出的 <code class="language-plaintext highlighter-rouge">N</code> 值. <code class="language-plaintext highlighter-rouge">constexpr</code> 关键字确保了只要传入的数组大小是编译期已知的, 整个函数调用就会在编译时完成, 直接将结果（一个常量）嵌入到代码中, 没有任何运行时开销.</li>
</ol>

<h3 id="现代化c实践-使用-stdsize">现代化C++实践: 使用 <code class="language-plaintext highlighter-rouge">std::size</code></h3>

<p>上述模板函数的实现是一种非常重要的C++编程模式. 自C++17起, 这个功能已被标准化, 并作为 <code class="language-plaintext highlighter-rouge">std::size</code> 函数提供在 <code class="language-plaintext highlighter-rouge">&lt;array&gt;</code> 和 <code class="language-plaintext highlighter-rouge">&lt;iterator&gt;</code> 头文件中.</p>

<p>在现代C++项目中, 推荐直接使用 <code class="language-plaintext highlighter-rouge">std::size</code>:</p>

<div class="language-cpp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="cp">#include</span> <span class="cpf">&lt;iterator&gt;</span><span class="c1"> // C++17 or &lt;array&gt; in C++20</span><span class="cp">
#include</span> <span class="cpf">&lt;iostream&gt;</span><span class="cp">
</span>
<span class="kt">int</span> <span class="nf">main</span><span class="p">()</span> <span class="p">{</span>
    <span class="kt">int</span> <span class="n">numbers</span><span class="p">[]</span> <span class="o">=</span> <span class="p">{</span><span class="mi">1</span><span class="p">,</span> <span class="mi">2</span><span class="p">,</span> <span class="mi">3</span><span class="p">,</span> <span class="mi">4</span><span class="p">,</span> <span class="mi">5</span><span class="p">};</span>
    <span class="n">std</span><span class="o">::</span><span class="n">cout</span> <span class="o">&lt;&lt;</span> <span class="s">"使用 std::size 获取大小: "</span> <span class="o">&lt;&lt;</span> <span class="n">std</span><span class="o">::</span><span class="n">size</span><span class="p">(</span><span class="n">numbers</span><span class="p">)</span> <span class="o">&lt;&lt;</span> <span class="n">std</span><span class="o">::</span><span class="n">endl</span><span class="p">;</span> <span class="c1">// 输出 5</span>
<span class="p">}</span>
</code></pre></div></div>

<p><code class="language-plaintext highlighter-rouge">std::size</code> 的底层实现原理与我们手动编写的 <code class="language-plaintext highlighter-rouge">getArraySize</code> 模板函数是相同的.</p>

<h3 id="总结">总结</h3>

<ul>
  <li><strong>数组退化</strong>: C++中, 按值传递的数组形参会被调整为指针, 导致尺寸信息丢失.</li>
  <li><strong>数组引用</strong>: 通过将形参声明为 <code class="language-plaintext highlighter-rouge">T (&amp;a)[N]</code>, 可以阻止数组退化, 保留完整的类型信息.</li>
  <li><strong>模板推导</strong>: 结合模板, 可以从不退化的数组类型中自动推导出其大小 <code class="language-plaintext highlighter-rouge">N</code>.</li>
  <li><strong><code class="language-plaintext highlighter-rouge">std::size</code></strong>: C++17标准提供了 <code class="language-plaintext highlighter-rouge">std::size</code> 函数, 作为获取原生数组大小的标准、安全且高效的方式.</li>
</ul>]]></content><author><name>Mikami</name></author><category term="cpp" /><category term="cpp" /><category term="code" /><summary type="html"><![CDATA[在C++中, 将原生数组传递给函数时, 一个常见的问题是在函数内部无法正确获取数组的原始大小.]]></summary></entry><entry><title type="html">C++的constexpr关键字</title><link href="https://mikami-w.github.io/cpp/2025/10/08/C++%E7%9A%84constexpr%E5%85%B3%E9%94%AE%E5%AD%97.html" rel="alternate" type="text/html" title="C++的constexpr关键字" /><published>2025-10-08T00:00:00+00:00</published><updated>2025-10-08T00:00:00+00:00</updated><id>https://mikami-w.github.io/cpp/2025/10/08/C++%E7%9A%84constexpr%E5%85%B3%E9%94%AE%E5%AD%97</id><content type="html" xml:base="https://mikami-w.github.io/cpp/2025/10/08/C++%E7%9A%84constexpr%E5%85%B3%E9%94%AE%E5%AD%97.html"><![CDATA[<h3 id="1-c-中-constexpr-的用法与作用">1. C++ 中 <code class="language-plaintext highlighter-rouge">constexpr</code> 的用法与作用</h3>

<h4 id="核心作用-purpose"><strong>核心作用 (Purpose)</strong></h4>

<p><code class="language-plaintext highlighter-rouge">constexpr</code> 的核心作用是将计算尽可能地从<strong>运行时 (runtime)</strong> 提前到<strong>编译时 (compile time)</strong>. 这样做带来了四大好处:</p>

<ol>
  <li><strong>性能提升</strong>: 编译时完成的计算, 程序运行时无需再做, 直接使用结果, 从而提升了执行效率.</li>
  <li><strong>编译期检查</strong>: 可以在编译阶段使用 <code class="language-plaintext highlighter-rouge">static_assert</code> 对计算结果进行验证, 将潜在的运行时错误转变为编译错误.</li>
  <li><strong>常量保证</strong>: <code class="language-plaintext highlighter-rouge">constexpr</code> 变量是真正的编译期常量, 可以用于数组大小、模板参数等必须在编译时确定的场景.</li>
  <li><strong>增强元编程</strong>: 使得在编译期间执行更复杂的算法成为可能.</li>
</ol>

<h4 id="主要用法-usage"><strong>主要用法 (Usage)</strong></h4>

<p><code class="language-plaintext highlighter-rouge">constexpr</code> 可以修饰变量、函数和构造函数:</p>

<ul>
  <li>
    <p><strong><code class="language-plaintext highlighter-rouge">constexpr</code> 变量</strong>: 必须在声明时用一个“常量表达式”来初始化. 它天生就是 <code class="language-plaintext highlighter-rouge">const</code> 的.</p>
  </li>
  <li>
    <p><strong><code class="language-plaintext highlighter-rouge">constexpr</code> 函数</strong>: 是一种“两用函数”:</p>

    <ul>
      <li>当传入的参数都是<strong>编译期常量</strong>时, 它就在<strong>编译时</strong>执行.</li>
      <li>当传入的参数包含<strong>运行时变量</strong>时, 它就和普通函数一样在<strong>运行时</strong>执行.</li>
      <li>可以把 lambda 表达式声明为<code class="language-plaintext highlighter-rouge">constexpr</code>:
        <div class="language-cpp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">auto</span> <span class="n">infDist</span> <span class="o">=</span> <span class="p">[](</span><span class="kt">int</span> <span class="n">val</span><span class="p">)</span> <span class="k">constexpr</span> <span class="o">-&gt;</span> <span class="kt">int</span> <span class="p">{...}</span>
</code></pre></div>        </div>
      </li>
    </ul>
  </li>
  <li>
    <p><strong>与 <code class="language-plaintext highlighter-rouge">const</code> 的关键区别</strong>:</p>

    <ul>
      <li><code class="language-plaintext highlighter-rouge">const</code> 强调<strong>不变性 (Immutability)</strong>: 变量初始化后值不能再改变, 但初始化可以在运行时进行.</li>
      <li><code class="language-plaintext highlighter-rouge">constexpr</code> 强调<strong>编译期可知性 (Compile-time Knowability)</strong>: 变量的值必须在编译时就能确定.</li>
    </ul>
  </li>
</ul>

<h4 id="规则随-c-标准演进"><strong>规则（随 C++ 标准演进）</strong></h4>

<ul>
  <li><strong>C++11</strong>：非常严格。函数体内只能包含一条 <code class="language-plaintext highlighter-rouge">return</code> 语句，不能有局部变量、循环、if/else（可以用三元运算符 <code class="language-plaintext highlighter-rouge">?:</code> 替代）等。</li>
  <li><strong>C++14</strong>：限制被大大放宽。函数体内可以包含局部变量、循环 (<code class="language-plaintext highlighter-rouge">for</code>, <code class="language-plaintext highlighter-rouge">while</code>)、条件分支 (<code class="language-plaintext highlighter-rouge">if</code>, <code class="language-plaintext highlighter-rouge">switch</code>) 等，使其几乎和普通函数一样灵活。</li>
  <li><strong>C++17 及以后</strong>：进一步放宽，例如允许在 <code class="language-plaintext highlighter-rouge">constexpr</code> 函数中使用 lambda 表达式。</li>
</ul>

<div class="language-cpp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// C++11 风格 (使用递归)</span>
<span class="k">constexpr</span> <span class="kt">long</span> <span class="kt">long</span> <span class="nf">factorial_cpp11</span><span class="p">(</span><span class="kt">int</span> <span class="n">n</span><span class="p">)</span> <span class="p">{</span>
    <span class="k">return</span> <span class="n">n</span> <span class="o">&lt;=</span> <span class="mi">1</span> <span class="o">?</span> <span class="mi">1</span> <span class="o">:</span> <span class="p">(</span><span class="n">n</span> <span class="o">*</span> <span class="n">factorial_cpp11</span><span class="p">(</span><span class="n">n</span> <span class="o">-</span> <span class="mi">1</span><span class="p">));</span>
<span class="p">}</span>

<span class="c1">// C++14 风格 (可以使用循环)</span>
<span class="k">constexpr</span> <span class="kt">long</span> <span class="kt">long</span> <span class="n">factorial_cpp14</span><span class="p">(</span><span class="kt">int</span> <span class="n">n</span><span class="p">)</span> <span class="p">{</span>
    <span class="kt">long</span> <span class="kt">long</span> <span class="n">result</span> <span class="o">=</span> <span class="mi">1</span><span class="p">;</span>
    <span class="k">for</span> <span class="p">(</span><span class="kt">int</span> <span class="n">i</span> <span class="o">=</span> <span class="mi">2</span><span class="p">;</span> <span class="n">i</span> <span class="o">&lt;=</span> <span class="n">n</span><span class="p">;</span> <span class="o">++</span><span class="n">i</span><span class="p">)</span> <span class="p">{</span>
        <span class="n">result</span> <span class="o">*=</span> <span class="n">i</span><span class="p">;</span>
    <span class="p">}</span>
    <span class="k">return</span> <span class="n">result</span><span class="p">;</span>
<span class="p">}</span>

<span class="kt">void</span> <span class="n">usage_example</span><span class="p">()</span> <span class="p">{</span>
    <span class="c1">// 编译时计算</span>
    <span class="k">constexpr</span> <span class="kt">long</span> <span class="kt">long</span> <span class="n">f5</span> <span class="o">=</span> <span class="n">factorial_cpp14</span><span class="p">(</span><span class="mi">5</span><span class="p">);</span> <span class="c1">// 结果 120 在编译时就算好了</span>
    <span class="k">static_assert</span><span class="p">(</span><span class="n">f5</span> <span class="o">==</span> <span class="mi">120</span><span class="p">,</span> <span class="s">"Calculation error!"</span><span class="p">);</span> <span class="c1">// 可以在编译时断言</span>

    <span class="kt">int</span> <span class="n">arr</span><span class="p">[</span><span class="n">f5</span><span class="p">];</span> <span class="c1">// OK, f5是常量表达式，可以用来定义数组大小</span>

    <span class="c1">// 运行时计算</span>
    <span class="kt">int</span> <span class="n">x</span><span class="p">;</span>
    <span class="n">std</span><span class="o">::</span><span class="n">cin</span> <span class="o">&gt;&gt;</span> <span class="n">x</span><span class="p">;</span>
    <span class="kt">long</span> <span class="kt">long</span> <span class="n">fx</span> <span class="o">=</span> <span class="n">factorial_cpp14</span><span class="p">(</span><span class="n">x</span><span class="p">);</span> <span class="c1">// x是运行时变量，函数在运行时执行</span>
    <span class="n">std</span><span class="o">::</span><span class="n">cout</span> <span class="o">&lt;&lt;</span> <span class="s">"Factorial of "</span> <span class="o">&lt;&lt;</span> <span class="n">x</span> <span class="o">&lt;&lt;</span> <span class="s">" is "</span> <span class="o">&lt;&lt;</span> <span class="n">fx</span> <span class="o">&lt;&lt;</span> <span class="n">std</span><span class="o">::</span><span class="n">endl</span><span class="p">;</span>
<span class="p">}</span>
</code></pre></div></div>

<h3 id="2-constexpr-在编译期的执行者与执行方式">2. <code class="language-plaintext highlighter-rouge">constexpr</code> 在编译期的执行者与执行方式</h3>

<h4 id="由谁执行who"><strong>由谁执行？(Who)</strong></h4>

<p><code class="language-plaintext highlighter-rouge">constexpr</code> 函数在编译期的执行者是 <strong>C++ 编译器本身 (the C++ compiler)</strong>.</p>

<p>现代编译器内部集成了一个 C++ 的解释器或虚拟机, 专门用于在编译过程中执行这类计算任务.</p>

<h4 id="如何执行how"><strong>如何执行？(How)</strong></h4>

<p>这个过程可以概括为<strong>“触发、执行、替换”</strong>三部曲:</p>

<ol>
  <li><strong>触发 (Trigger)</strong>: 当编译器在代码中遇到一个<strong>必须是常量表达式</strong>的上下文时（例如初始化 <code class="language-plaintext highlighter-rouge">constexpr</code> 变量、<code class="language-plaintext highlighter-rouge">static_assert</code> 的条件、数组维度等）, 就会触发对 <code class="language-plaintext highlighter-rouge">constexpr</code> 函数的编译期求值.</li>
  <li><strong>执行 (Execution)</strong>: 一旦触发, 编译器在其内部的一个高度受限的<strong>“沙盒”环境</strong>中模拟执行该函数. 这个执行环境非常特殊，可以理解为一个高度受限的“沙盒”：
    <ul>
      <li><strong>纯计算环境</strong>：它只能访问在编译期已知的数据，比如传入的常量参数（如 <code class="language-plaintext highlighter-rouge">5</code>）和函数内部的计算。</li>
      <li><strong>没有外部依赖</strong>：它完全与外部世界隔离。不能进行任何 I/O 操作（如 <code class="language-plaintext highlighter-rouge">cout</code>, <code class="language-plaintext highlighter-rouge">cin</code>）、不能读写文件、不能进行动态内存分配 (<code class="language-plaintext highlighter-rouge">new</code>, <code class="language-plaintext highlighter-rouge">delete</code>)、不能调用系统 API、不能产生随机数等。</li>
      <li><strong>确定性</strong>：对于相同的输入，结果必须永远相同。这就是为什么所有依赖运行时状态的操作都被禁止的原因。</li>
    </ul>
  </li>
  <li><strong>替换 (Replacement)</strong>: 函数执行完毕后, 编译器会得到一个确定的常量结果. 然后, 它会用这个<strong>结果值</strong>直接<strong>替换</strong>掉源代码中原来调用函数的那段表达式. 最终生成的可执行代码中, 将不包含这个函数调用, 只有一个硬编码的常量.</li>
</ol>

<p>例如, <code class="language-plaintext highlighter-rouge">int arr[factorial(5)];</code> 在编译后, 效果等同于 <code class="language-plaintext highlighter-rouge">int arr[120];</code>.</p>]]></content><author><name>Mikami</name></author><category term="cpp" /><category term="cpp" /><category term="code" /><summary type="html"><![CDATA[1. C++ 中 constexpr 的用法与作用]]></summary></entry><entry><title type="html">C++虚函数原理与内存布局</title><link href="https://mikami-w.github.io/cpp/2025/10/08/C++%E8%99%9A%E5%87%BD%E6%95%B0%E5%8E%9F%E7%90%86.html" rel="alternate" type="text/html" title="C++虚函数原理与内存布局" /><published>2025-10-08T00:00:00+00:00</published><updated>2025-10-08T00:00:00+00:00</updated><id>https://mikami-w.github.io/cpp/2025/10/08/C++%E8%99%9A%E5%87%BD%E6%95%B0%E5%8E%9F%E7%90%86</id><content type="html" xml:base="https://mikami-w.github.io/cpp/2025/10/08/C++%E8%99%9A%E5%87%BD%E6%95%B0%E5%8E%9F%E7%90%86.html"><![CDATA[<p>虚函数是如何实现动态绑定的呢? 这背后是编译器为我们做的一些工作, 主要依赖两个概念: <strong>虚函数表 (Virtual Function Table, vtable)</strong> 和 <strong>虚函数指针 (Virtual Function Pointer, vptr)</strong>.</p>

<div class="language-cpp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">class</span> <span class="nc">Shape</span> <span class="p">{</span>
<span class="nl">public:</span>
    <span class="c1">// 在基类中声明为虚函数</span>
    <span class="k">virtual</span> <span class="kt">void</span> <span class="n">draw</span><span class="p">()</span> <span class="k">const</span> <span class="p">{</span>
        <span class="n">std</span><span class="o">::</span><span class="n">cout</span> <span class="o">&lt;&lt;</span> <span class="s">"Drawing a generic shape."</span> <span class="o">&lt;&lt;</span> <span class="n">std</span><span class="o">::</span><span class="n">endl</span><span class="p">;</span>
    <span class="p">}</span>
    
    <span class="c1">// 基类的析构函数通常也应该是虚函数(为了能够使用基类指针正确析构派生类对象)</span>
    <span class="k">virtual</span> <span class="o">~</span><span class="n">Shape</span><span class="p">()</span> <span class="p">{}</span> 
<span class="p">};</span>

<span class="k">class</span> <span class="nc">Circle</span> <span class="o">:</span> <span class="k">public</span> <span class="n">Shape</span> <span class="p">{</span>
<span class="nl">public:</span>
    <span class="c1">// 在派生类中重写(override)该函数</span>
    <span class="c1">// 'override' 关键字不是必须的, 但是一个好习惯, 它可以让编译器检查你是否真的重写了基类的虚函数</span>
    <span class="kt">void</span> <span class="n">draw</span><span class="p">()</span> <span class="k">const</span> <span class="k">override</span> <span class="p">{</span>
        <span class="n">std</span><span class="o">::</span><span class="n">cout</span> <span class="o">&lt;&lt;</span> <span class="s">"Drawing a circle."</span> <span class="o">&lt;&lt;</span> <span class="n">std</span><span class="o">::</span><span class="n">endl</span><span class="p">;</span>
    <span class="p">}</span>
<span class="p">};</span>

<span class="k">class</span> <span class="nc">Rectangle</span> <span class="o">:</span> <span class="k">public</span> <span class="n">Shape</span> <span class="p">{</span>
<span class="nl">public:</span>
    <span class="kt">void</span> <span class="n">draw</span><span class="p">()</span> <span class="k">const</span> <span class="k">override</span> <span class="p">{</span>
        <span class="n">std</span><span class="o">::</span><span class="n">cout</span> <span class="o">&lt;&lt;</span> <span class="s">"Drawing a rectangle."</span> <span class="o">&lt;&lt;</span> <span class="n">std</span><span class="o">::</span><span class="n">endl</span><span class="p">;</span>
    <span class="p">}</span>
<span class="p">};</span>

<span class="kt">void</span> <span class="n">drawShape</span><span class="p">(</span><span class="k">const</span> <span class="n">Shape</span><span class="o">&amp;</span> <span class="n">shape</span><span class="p">)</span> <span class="p">{</span>
    <span class="n">shape</span><span class="p">.</span><span class="n">draw</span><span class="p">();</span> <span class="c1">// 现在这里会发生动态绑定</span>
<span class="p">}</span>
</code></pre></div></div>

<h4 id="1-虚函数表-vtable">1. 虚函数表 (vtable)</h4>

<ul>
  <li>当一个类声明了虚函数（或者继承了有虚函数的基类）, 编译器会为这个 <strong>类</strong> 创建一个静态的数组, 这个数组就是 <strong>虚函数表 (vtable)</strong>.</li>
  <li>vtable 中存放的是该类所有虚函数的 <strong>地址</strong>.</li>
  <li>如果派生类重写了基类的虚函数, 那么在派生类的vtable中, <strong>相应的位置会被替换为派生类重写的那个函数的地址</strong>.</li>
</ul>

<p><strong>图示:</strong></p>

<ul>
  <li><strong><code class="language-plaintext highlighter-rouge">Shape</code> 的 vtable</strong>: <code class="language-plaintext highlighter-rouge">[ address of Shape::draw, address of Shape::~Shape ]</code></li>
  <li><strong><code class="language-plaintext highlighter-rouge">Circle</code> 的 vtable</strong>: <code class="language-plaintext highlighter-rouge">[ address of Circle::draw, address of Circle::~Circle ]</code> (draw函数地址被重写了)</li>
  <li><strong><code class="language-plaintext highlighter-rouge">Rectangle</code> 的 vtable</strong>: <code class="language-plaintext highlighter-rouge">[ address of Rectangle::draw, address of Rectangle::~Rectangle ]</code></li>
</ul>

<h4 id="2-虚函数指针-vptr">2. 虚函数指针 (vptr)</h4>

<ul>
  <li>当一个类拥有vtable时, 编译器会为这个类的 <strong>每一个对象</strong> 自动添加一个隐藏的成员, 这个成员就是一个指针, 叫做 <strong>虚函数指针 (vptr)</strong>.</li>
  <li>这个 <code class="language-plaintext highlighter-rouge">vptr</code> 在对象创建时（调用构造函数时）被初始化, 指向其所属类的 vtable.</li>
</ul>

<p><strong>图示:</strong></p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>// 当你创建一个 Circle 对象时
Circle c;

// c 对象的内存布局大致如下: 
[   vptr   ] --------&gt; [ Circle's vtable   ]
[ 成员变量... ]         [ &amp;Circle::draw     ]
                       [ &amp;Circle::~Circle  ]
                       [ ...               ]
</code></pre></div></div>

<h4 id="3-调用过程">3. 调用过程</h4>

<p>当执行 <code class="language-plaintext highlighter-rouge">shape_ptr-&gt;draw();</code> 这样的代码时, 实际发生的步骤如下:</p>

<ol>
  <li><strong>访问 vptr</strong>: 程序通过 <code class="language-plaintext highlighter-rouge">shape_ptr</code> 找到它所指向的对象（可能是 <code class="language-plaintext highlighter-rouge">Circle</code> 或 <code class="language-plaintext highlighter-rouge">Rectangle</code> 对象）.</li>
  <li><strong>找到 vtable</strong>: 程序读取该对象内部的 <code class="language-plaintext highlighter-rouge">vptr</code>, 找到对应的 vtable（如果是 <code class="language-plaintext highlighter-rouge">Circle</code> 对象, 就找到 <code class="language-plaintext highlighter-rouge">Circle</code> 的 vtable）.</li>
  <li><strong>调用函数</strong>: 程序在 vtable 中查找 <code class="language-plaintext highlighter-rouge">draw</code> 函数对应的地址（比如它在表中的第0个位置）, 然后调用该地址上的函数.</li>
</ol>

<p>由于 <code class="language-plaintext highlighter-rouge">Circle</code> 对象的 <code class="language-plaintext highlighter-rouge">vptr</code> 指向 <code class="language-plaintext highlighter-rouge">Circle</code> 的 vtable, 而 <code class="language-plaintext highlighter-rouge">Circle</code> 的 vtable 中存放的是 <code class="language-plaintext highlighter-rouge">Circle::draw</code> 的地址, 所以最终调用的就是 <code class="language-plaintext highlighter-rouge">Circle::draw()</code>.</p>

<p>整个过程在运行时完成, 所以实现了动态绑定.</p>

<h4 id="4-内存布局">4. 内存布局</h4>

<p>当类中出现虚函数时, 编译器会进行一些额外的处理来支持多态.</p>

<ol>
  <li>为每个拥有虚函数的 <strong>类</strong> 创建一个 <strong>虚函数表 (vtable)</strong>. 这个表是一个静态数组, 存储着虚函数的地址.</li>
  <li>在每个 <strong>对象</strong> 的内存布局的最前端, 插入一个 <strong>虚函数指针 (vptr)</strong>, 这个指针指向该对象所属类的 vtable.</li>
</ol>

<div class="language-cpp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">class</span> <span class="nc">Base_V</span> <span class="p">{</span>
<span class="nl">public:</span>
    <span class="kt">int</span> <span class="n">b_data</span><span class="p">;</span>
    <span class="k">virtual</span> <span class="kt">void</span> <span class="n">func</span><span class="p">()</span> <span class="p">{}</span>
    <span class="k">virtual</span> <span class="kt">void</span> <span class="n">funcWithoutOverride</span><span class="p">()</span> <span class="p">{}</span>
    <span class="k">virtual</span> <span class="o">~</span><span class="n">Base_V</span><span class="p">()</span> <span class="p">{}</span>
<span class="p">};</span>

<span class="k">class</span> <span class="nc">Derived_V</span> <span class="o">:</span> <span class="k">public</span> <span class="n">Base_V</span> <span class="p">{</span>
<span class="nl">public:</span>
    <span class="kt">char</span> <span class="n">d_data</span><span class="p">;</span>
    <span class="kt">void</span> <span class="n">func</span><span class="p">()</span> <span class="k">override</span> <span class="p">{}</span>
    <span class="o">~</span><span class="n">Derived_V</span><span class="p">()</span> <span class="k">override</span> <span class="p">{}</span>
<span class="p">};</span>
</code></pre></div></div>

<ul>
  <li><strong>虚函数表 (vtables)</strong></li>
</ul>

<p>vtable 是独立于对象存在的, 每个类只有一份.</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Base_V 的 vtable (vtable_Base_V)
+------------------------------+
| &amp;Base_V::func                |  (函数指针)
+------------------------------+
| &amp;Base_V::funcWithoutOverride |  (函数指针)
+------------------------------+
| &amp;Base_V::~Base_V             |  (函数指针)
+------------------------------+

Derived_V 的 vtable (vtable_Derived_V)
+------------------------------+
| &amp;Derived_V::func             |  (重写了, 地址指向派生类的函数)
+------------------------------+
| &amp;Base_V::funcWithoutOverride |  (未被重写, 地址仍指向基类的函数)
+------------------------------+
| &amp;Derived_V::~Derived_V       |  (重写了, 地址指向派生类的析构函数)
+------------------------------+
</code></pre></div></div>

<ul>
  <li><strong>基类对象 <code class="language-plaintext highlighter-rouge">Base_V b_v;</code></strong></li>
</ul>

<p>对象内存的开始处是一个 <code class="language-plaintext highlighter-rouge">vptr</code>, 指向 <code class="language-plaintext highlighter-rouge">Base_V</code> 的 vtable, 后面跟着成员变量.</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>对象 b_v 的内存布局
(大小: 8 (vptr) + 4 (int) = 12 字节, 对齐后可能是 16 字节)

+---------------------+
|       vptr          | -------&gt; [ vtable_Base_V ]
+---------------------+
|      b_data         | (int, 4 字节)
+---------------------+
|      (padding)      | (内存对齐填充, 可能 4 字节)
+---------------------+
</code></pre></div></div>

<ul>
  <li><strong>派生类对象 <code class="language-plaintext highlighter-rouge">Derived_V d_v;</code></strong></li>
</ul>

<p>同样, 派生类对象也包含一个基类子对象. <code class="language-plaintext highlighter-rouge">vptr</code> 也在最前面, 但最关键的是: <strong>这个 <code class="language-plaintext highlighter-rouge">vptr</code> 指向的是派生类 <code class="language-plaintext highlighter-rouge">Derived_V</code> 的 vtable</strong>. 这正是多态得以实现的核心.</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>对象 d_v 的内存布局
(大小: 8 (vptr) + 4 (int) + 1 (char) = 13 字节, 对齐后可能是 16 或 24 字节)

+----------------------+
|   |     vptr      |  | -------&gt; [ vtable_Derived_V ]
|   +---------------+  |
|   --- 基类部分 ---    |
|   +---------------+  |
|   |    b_data     |  | (int, 4 字节)
|   +---------------+  |
|   --- 派生类部分 ---   |
|   +---------------+  |
|   |    d_data     |  | (char, 1 字节)
|   +---------------+  |
|   |   (padding)   |  | (内存对齐填充)
+----------------------+
</code></pre></div></div>

<p>当执行 <code class="language-plaintext highlighter-rouge">Base_V* ptr = new Derived_V(); ptr-&gt;func();</code> 时:</p>

<ol>
  <li><code class="language-plaintext highlighter-rouge">ptr</code> 指向 <code class="language-plaintext highlighter-rouge">d_v</code> 对象的起始地址.</li>
  <li>通过 <code class="language-plaintext highlighter-rouge">ptr</code> 找到对象头部的 <code class="language-plaintext highlighter-rouge">vptr</code>.</li>
  <li><code class="language-plaintext highlighter-rouge">vptr</code> 指向 <code class="language-plaintext highlighter-rouge">vtable_Derived_V</code>.</li>
  <li>在 <code class="language-plaintext highlighter-rouge">vtable_Derived_V</code> 中查找 <code class="language-plaintext highlighter-rouge">func()</code> 对应的函数指针, 这个指针指向 <code class="language-plaintext highlighter-rouge">Derived_V::func()</code>.</li>
  <li>调用 <code class="language-plaintext highlighter-rouge">Derived_V::func()</code>.</li>
</ol>

<h4 id="通过打印地址来认识虚函数表">通过打印地址来认识虚函数表</h4>

<div class="language-cpp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="cp">#include</span> <span class="cpf">&lt;iostream&gt;</span><span class="cp">
</span>
<span class="k">class</span> <span class="nc">base</span>
<span class="p">{</span>
<span class="nl">public:</span>
    <span class="kt">void</span> <span class="n">print_nv</span><span class="p">()</span>
    <span class="p">{</span>
        <span class="n">std</span><span class="o">::</span><span class="n">cout</span> <span class="o">&lt;&lt;</span> <span class="s">"base::print_nv"</span> <span class="o">&lt;&lt;</span> <span class="n">std</span><span class="o">::</span><span class="n">endl</span><span class="p">;</span>
    <span class="p">}</span>

    <span class="k">virtual</span> <span class="kt">void</span> <span class="n">print_v</span><span class="p">()</span>
    <span class="p">{</span>
        <span class="n">std</span><span class="o">::</span><span class="n">cout</span> <span class="o">&lt;&lt;</span> <span class="s">"base::print_v"</span> <span class="o">&lt;&lt;</span> <span class="n">std</span><span class="o">::</span><span class="n">endl</span><span class="p">;</span>
    <span class="p">}</span>

    <span class="k">virtual</span> <span class="o">~</span><span class="n">base</span><span class="p">()</span> <span class="o">=</span> <span class="k">default</span><span class="p">;</span>

<span class="nl">private:</span>
    <span class="kt">int</span> <span class="n">a</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span>
<span class="p">};</span>

<span class="k">class</span> <span class="nc">derived</span> <span class="o">:</span> <span class="k">public</span> <span class="n">base</span> <span class="p">{...};</span>

<span class="kt">int</span> <span class="n">main</span><span class="p">()</span>
<span class="p">{</span>
    <span class="n">base</span><span class="o">*</span> <span class="n">pArr</span><span class="p">[</span><span class="mi">2</span><span class="p">]{</span><span class="k">new</span> <span class="n">base</span><span class="p">(),</span> <span class="k">new</span> <span class="n">derived</span><span class="p">};</span>

    <span class="c1">// 下面两行注释输出1, 因为重载决议中匹配成员函数指针的 operator&lt;&lt; 函数只有将指针转换为 bool 后输出</span>
    <span class="c1">// std::cout &lt;&lt; "address of base::print_v: " &lt;&lt; reinterpret_cast&lt;void*&gt;(&amp;base::print_v) &lt;&lt; std::endl; // print: 1</span>
    <span class="c1">// std::cout &lt;&lt; "address of derived::print_v: " &lt;&lt; &amp;derived::print_v &lt;&lt; std::endl; // print: 1</span>
    
    <span class="n">std</span><span class="o">::</span><span class="n">cout</span> <span class="o">&lt;&lt;</span> <span class="s">"wtf is void(base::*)()"</span> <span class="o">&lt;&lt;</span> <span class="n">std</span><span class="o">::</span><span class="n">endl</span><span class="p">;</span> <span class="c1">// 成员函数指针</span>
    <span class="n">std</span><span class="o">::</span><span class="n">cout</span> <span class="o">&lt;&lt;</span> <span class="s">"sizeof(void(base::*)()): "</span> <span class="o">&lt;&lt;</span> <span class="k">sizeof</span><span class="p">(</span><span class="kt">void</span><span class="p">(</span><span class="n">base</span><span class="o">::*</span><span class="p">)())</span> <span class="o">&lt;&lt;</span> <span class="n">std</span><span class="o">::</span><span class="n">endl</span><span class="p">;</span> <span class="c1">// 16</span>
    <span class="n">std</span><span class="o">::</span><span class="n">cout</span> <span class="o">&lt;&lt;</span> <span class="n">std</span><span class="o">::</span><span class="n">endl</span><span class="p">;</span>

    <span class="n">std</span><span class="o">::</span><span class="n">cout</span> <span class="o">&lt;&lt;</span> <span class="n">pArr</span><span class="p">[</span><span class="mi">0</span><span class="p">]</span> <span class="o">&lt;&lt;</span> <span class="s">"</span><span class="se">\t\t</span><span class="s">: address of *pArr[0] and also vptr"</span> <span class="o">&lt;&lt;</span> <span class="n">std</span><span class="o">::</span><span class="n">endl</span><span class="p">;</span>
    <span class="n">std</span><span class="o">::</span><span class="n">cout</span> <span class="o">&lt;&lt;</span> <span class="o">*</span><span class="p">(</span><span class="k">reinterpret_cast</span><span class="o">&lt;</span><span class="kt">void</span><span class="o">****&gt;</span><span class="p">(</span><span class="n">pArr</span><span class="p">[</span><span class="mi">0</span><span class="p">]))</span>
        <span class="o">&lt;&lt;</span> <span class="s">"</span><span class="se">\t\t</span><span class="s">: value of vptr, also address of vtable and function base::print_v"</span> <span class="o">&lt;&lt;</span> <span class="n">std</span><span class="o">::</span><span class="n">endl</span><span class="p">;</span>
    <span class="n">std</span><span class="o">::</span><span class="n">cout</span> <span class="o">&lt;&lt;</span> <span class="o">**</span><span class="p">(</span><span class="k">reinterpret_cast</span><span class="o">&lt;</span><span class="kt">void</span><span class="o">****&gt;</span><span class="p">(</span><span class="n">pArr</span><span class="p">[</span><span class="mi">0</span><span class="p">]))</span>
        <span class="o">&lt;&lt;</span> <span class="s">"</span><span class="se">\t\t</span><span class="s">: vtable[0], value of base::print_v"</span> <span class="o">&lt;&lt;</span> <span class="n">std</span><span class="o">::</span><span class="n">endl</span><span class="p">;</span>
    <span class="n">std</span><span class="o">::</span><span class="n">cout</span> <span class="o">&lt;&lt;</span> <span class="o">***</span><span class="p">(</span><span class="k">reinterpret_cast</span><span class="o">&lt;</span><span class="kt">void</span><span class="o">****&gt;</span><span class="p">(</span><span class="n">pArr</span><span class="p">[</span><span class="mi">0</span><span class="p">]))</span>
        <span class="o">&lt;&lt;</span> <span class="s">"</span><span class="se">\t</span><span class="s">: 8 bytes of machine code in function base::print_v"</span> <span class="o">&lt;&lt;</span> <span class="n">std</span><span class="o">::</span><span class="n">endl</span><span class="p">;</span>

    <span class="k">delete</span> <span class="n">pArr</span><span class="p">[</span><span class="mi">0</span><span class="p">];</span>
    <span class="k">delete</span> <span class="n">pArr</span><span class="p">[</span><span class="mi">1</span><span class="p">];</span>
    <span class="k">return</span> <span class="mi">0</span><span class="p">;</span>
<span class="p">}</span>
</code></pre></div></div>

<p>输出:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>wtf is void(base::*)()
sizeof(void(base::*)()): 16

0x25540531a80           : address of *pArr[0] and also vptr
0x7ff6242c46d0          : value of vptr, also address of vtable and function base::print_v
0x7ff6242c2910          : vtable[0], value of base::print_v
0x20ec8348e5894855      : 8 bytes of machine code in function base::print_v
</code></pre></div></div>]]></content><author><name>Mikami</name></author><category term="cpp" /><category term="cpp" /><category term="code" /><summary type="html"><![CDATA[虚函数是如何实现动态绑定的呢? 这背后是编译器为我们做的一些工作, 主要依赖两个概念: 虚函数表 (Virtual Function Table, vtable) 和 虚函数指针 (Virtual Function Pointer, vptr).]]></summary></entry></feed>