<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
  <author>
    <name>Sean</name>
  </author>
  <generator uri="https://hexo.io/">Hexo</generator>
  <icon>https://devilsean.github.io/img/favicon.ico</icon>
  <id>https://devilsean.github.io/</id>
  <link href="https://devilsean.github.io/" rel="alternate"/>
  <link href="https://devilsean.github.io/atom.xml" rel="self"/>
  <rights>All rights reserved 2026, Sean</rights>
  <subtitle>像风走了八千里，不问归期</subtitle>
  <title>肖恩的博客</title>
  <updated>2026-10-30T11:04:53.774Z</updated>
  <entry>
    <author>
      <name>Sean</name>
    </author>
    <category term="Effective Modern C++" scheme="https://devilsean.github.io/categories/Effective-Modern-C/"/>
    <category term="1. 类别推导" scheme="https://devilsean.github.io/categories/Effective-Modern-C/1-%E7%B1%BB%E5%88%AB%E6%8E%A8%E5%AF%BC/"/>
    <category term="C++" scheme="https://devilsean.github.io/tags/C/"/>
    <category term="模板类型推导" scheme="https://devilsean.github.io/tags/%E6%A8%A1%E6%9D%BF%E7%B1%BB%E5%9E%8B%E6%8E%A8%E5%AF%BC/"/>
    <category term="类别推导" scheme="https://devilsean.github.io/tags/%E7%B1%BB%E5%88%AB%E6%8E%A8%E5%AF%BC/"/>
    <content>
      <![CDATA[<p>函数模板形如：</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">template</span>&lt;<span class="keyword">typename</span> T&gt; </span></span><br><span class="line"><span class="function"><span class="type">void</span> <span class="title">f</span><span class="params">(ParamType param)</span></span>;</span><br></pre></td></tr></table></figure><p>一次调用形如：</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">f</span>(expr); <span class="comment">//以某表达式调用 f</span></span><br></pre></td></tr></table></figure><p>在编译期，编译器会通过<code>expr</code>推导<code>T</code>的类型和<code>ParamType</code>的类型（通常会包含一些修饰，例如<code>const</code>或引用符号等限定）。</p><p><code>T</code>的类型推导结果分以下三种情况讨论：</p><h2 id="1-ParamType-具有指针或引用型别，但不是万能引用"><a href="#1-ParamType-具有指针或引用型别，但不是万能引用" class="headerlink" title="1. ParamType 具有指针或引用型别，但不是万能引用"></a>1. <code>ParamType</code> 具有指针或引用型别，但不是万能引用</h2><p>类型推导规则如下：</p><ol><li>若<code>expr</code>具有引用类型，先将引用部分忽略。</li><li>然后，对<code>expr</code>的类型和<code>ParamType</code>的类型执行模式匹配，来决定<code>T</code>的类型。</li></ol><h4 id="举例"><a href="#举例" class="headerlink" title="举例"></a>举例</h4><p>模式声明如下：</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">template</span>&lt;<span class="keyword">typename</span> T&gt; </span></span><br><span class="line"><span class="function"><span class="type">void</span> <span class="title">f</span><span class="params">(T&amp; param)</span></span>; <span class="comment">// param 是个引用</span></span><br></pre></td></tr></table></figure><p>声明下列变量：</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="type">int</span> x = <span class="number">27</span>; <span class="comment">// x 的类型是 int</span></span><br><span class="line"><span class="type">const</span> <span class="type">int</span> xx= x; <span class="comment">// cx 的类型是 const int</span></span><br><span class="line"><span class="type">const</span> <span class="type">int</span>&amp; rx = x; <span class="comment">// rx 是 x 的类型为 const int 的引用</span></span><br></pre></td></tr></table></figure><p>各次调用的推导结果如下：</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">f</span>(x); <span class="comment">// T 的类型是 int，param 的类型是 int&amp; </span></span><br><span class="line"><span class="built_in">f</span>(cx); <span class="comment">// T 的类型是 const int，param 的类型是 const int&amp; </span></span><br><span class="line"><span class="built_in">f</span>(rx); <span class="comment">// T 的类型是 const int，param 的类型是 const int&amp;</span></span><br></pre></td></tr></table></figure><p>右值和指针方式同理：</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">template</span>&lt;<span class="keyword">typename</span> T&gt; </span></span><br><span class="line"><span class="function"><span class="type">void</span> <span class="title">f</span><span class="params">(T* param)</span></span>; <span class="comment">// param 是个指针</span></span><br><span class="line"></span><br><span class="line"><span class="type">int</span> x = <span class="number">27</span>; </span><br><span class="line"><span class="type">const</span> <span class="type">int</span> *px = &amp;x; <span class="comment">// px 是指向 x 的指针，类型为 const int</span></span><br><span class="line"> </span><br><span class="line"><span class="built_in">f</span>(&amp;x); <span class="comment">// T 的类型是 int, param 的类型是 int* </span></span><br><span class="line"><span class="built_in">f</span>(px); <span class="comment">// T 的类型是 const int, param 的类型是 const int*</span></span><br></pre></td></tr></table></figure><h2 id="2-ParamType-是万能引用"><a href="#2-ParamType-是万能引用" class="headerlink" title="2. ParamType 是万能引用"></a>2. <code>ParamType</code> 是万能引用</h2><p>类型推导规则如下：</p><ol><li>如果 <code>expr</code> 是个左值， <code>T</code> 和 <code>ParamType</code> 都会被推导为左值引用。</li><li>如果 <code>expr</code> 是个右值，则应用“常规＂（即情况 1 中的）规则。</li></ol><h4 id="举例-1"><a href="#举例-1" class="headerlink" title="举例"></a>举例</h4><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">template</span>&lt;<span class="keyword">typename</span> T&gt; </span></span><br><span class="line"><span class="function"><span class="type">void</span> <span class="title">f</span><span class="params">(T&amp;&amp; param)</span></span>; <span class="comment">// param 是个万能引用</span></span><br><span class="line"></span><br><span class="line"><span class="type">int</span> x = <span class="number">27</span>; </span><br><span class="line"><span class="type">const</span> <span class="type">int</span> cx= x; </span><br><span class="line"><span class="type">const</span> <span class="type">int</span>&amp; rx = x;</span><br><span class="line"></span><br><span class="line"><span class="built_in">f</span>(x); <span class="comment">// x 是个左值，所以 T 的类型是 int&amp;, param 的类型也是 int&amp;</span></span><br><span class="line"><span class="built_in">f</span>(cx); <span class="comment">// cx 是个左值，所以 T 的类型是 const int&amp;, param 的类型也是 const int&amp;</span></span><br><span class="line"><span class="built_in">f</span>(rx); <span class="comment">// rx 是个左值，所以 T 的类型是 const int&amp;, param 的类型也是 const int&amp;</span></span><br><span class="line"><span class="built_in">f</span>(<span class="number">27</span>); <span class="comment">// 27 是个右值，所以 T 的类型是 int, param 的类型就成了 int&amp;&amp;</span></span><br></pre></td></tr></table></figure><h2 id="3-ParamType-既非指针也非引用"><a href="#3-ParamType-既非指针也非引用" class="headerlink" title="3. ParamType 既非指针也非引用"></a>3. <code>ParamType</code> 既非指针也非引用</h2><p>即按值传递，无论传入的是什么，<code>param</code>都会是它的副本，是一个新对象：</p><ol><li>若 <code>expr</code> 具有引用类型，则忽略其引用部分。</li><li>忽略 <code>expr</code> 的引用性之后，若 <code>expr</code> 是个 <code>const</code> 对象，也忽略。若其是个 <code>volatile</code> 对象，同样忽略。</li></ol><h4 id="举例-2"><a href="#举例-2" class="headerlink" title="举例"></a>举例</h4><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">template</span>&lt;<span class="keyword">typename</span> T&gt;</span></span><br><span class="line"><span class="function"><span class="type">void</span> <span class="title">f</span><span class="params">(T param)</span></span>; <span class="comment">//param现在是按值传递</span></span><br><span class="line"></span><br><span class="line"><span class="type">int</span> x = <span class="number">27</span>; <span class="comment">//同前</span></span><br><span class="line"><span class="type">const</span> <span class="type">int</span> cx = x; <span class="comment">//同前</span></span><br><span class="line"><span class="type">const</span> <span class="type">int</span>&amp; rx = x; <span class="comment">//同前</span></span><br><span class="line"></span><br><span class="line"><span class="built_in">f</span>(x); <span class="comment">// T和param的类型都是int</span></span><br><span class="line"><span class="built_in">f</span>(cx); <span class="comment">// T和param的类型都是int</span></span><br><span class="line"><span class="built_in">f</span>(rx); <span class="comment">// T和param的类型都是int</span></span><br></pre></td></tr></table></figure><p>即使<code>cx</code>和<code>rx</code>代表const值，<code>param</code>仍然不具有<code>const</code>的类型。<code>param</code>是个完全独立于<code>cx</code>和<code>rx</code>的一个副本。从而<code>cx</code>和<code>rx</code>不可修改并不能说明<code>param</code>是否可以修改。</p><h2 id="数组实参"><a href="#数组实参" class="headerlink" title="数组实参"></a>数组实参</h2><p>一种边缘情况，数组类型有别于指针类型，但是在多数语境下，数组会退化成首元素的指针。</p><p>C++ 中没有真正的 “数组形参”，因为函数无法直接接收数组；当数组传递给<strong>按值形参</strong>的模板时，编译器会把形参里的数组声明直接当作指针处理。但是：</p><p>尽管函数无法声明真正的数组类型的形参，但是它们能够将形参声明为数组的<strong>引用</strong>。在这种情况下<code>T</code>的类型会被推导成实际的数组类型。这个类型中会包含数组尺寸。</p><p>利用声明数组引用能力，可以创造出一个模板用来推导数组中含有的元素个数：</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 以编译期常量形式返回数组尺寸</span></span><br><span class="line"><span class="comment">// （该数组形参未起名字，因为我们只关心共含有的元素个数） </span></span><br><span class="line"><span class="function"><span class="keyword">template</span>&lt;<span class="keyword">typename</span> T, std::<span class="type">size_t</span> N&gt; </span></span><br><span class="line"><span class="function"><span class="keyword">constexpr</span> std::<span class="type">size_t</span> <span class="title">arraySize</span><span class="params">(T (&amp;)[N])</span> <span class="keyword">noexcept</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line"> <span class="keyword">return</span> N;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>将函数声明为<code>constexpr</code>，能够使得其返回值在编译期就可以使用。从而在声明一个数组时，指定其尺寸和另一数组相同，而后者的尺寸则从花括号初始化形式计算得出：</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="type">int</span> keyVals[] = &#123; <span class="number">1</span>, <span class="number">3</span>, <span class="number">7</span>, <span class="number">9</span>, <span class="number">11</span>, <span class="number">22</span>, <span class="number">35</span> &#125;; <span class="comment">// keyVals 含有 7 个元素 </span></span><br><span class="line"><span class="type">int</span> mappedVals [<span class="built_in">arraySize</span>(keyVals)]; <span class="comment">// mappedVals 被指定与之相同</span></span><br></pre></td></tr></table></figure><h2 id="函数实参"><a href="#函数实参" class="headerlink" title="函数实参"></a>函数实参</h2><p>函数类型也同样会退化成函数指针，并且针对数组类型推导的一切都适用于函数及其向函数指针的退化。如下：</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="type">void</span> <span class="title">someFunc</span><span class="params">(<span class="type">int</span>, <span class="type">double</span>)</span></span>; <span class="comment">// someFunc 是个函数，其类型为 void(int, double)</span></span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">template</span>&lt;<span class="keyword">typename</span> T&gt; </span></span><br><span class="line"><span class="function"><span class="type">void</span> <span class="title">fl</span><span class="params">(T param)</span></span>; <span class="comment">// 在 fl 中，param 按值传递</span></span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">template</span>&lt;<span class="keyword">typename</span> T&gt; </span></span><br><span class="line"><span class="function"><span class="type">void</span> <span class="title">f2</span> <span class="params">(T&amp; param)</span></span>; <span class="comment">// 在 f2 中，param 按引用传递</span></span><br><span class="line"></span><br><span class="line"><span class="built_in">f1</span>(someFunc);  <span class="comment">// param 披推导为函数指针，具体类型是 void (*)(int, double)</span></span><br><span class="line"><span class="built_in">f2</span>(someFunc);  <span class="comment">// param 披推导为函数引用，具体类型是 void (&amp;)(int, double)</span></span><br></pre></td></tr></table></figure><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><ul><li>在模板类型推导时，有引用的实参会被视为无引用，他们的引用会被忽略</li><li>对于通用引用的推导，左值实参会被特殊对待</li><li>对于传值类型推导，<code>const</code>&#x2F;<code>volatile</code>实参会被认为是<code>non-const</code>的和<code>non-volatile</code>的</li><li>在模板类型推导时，数组名或者函数名实参会退化为指针，除非它们被用于初始化引用</li></ul>]]>
    </content>
    <id>https://devilsean.github.io/posts/22b7bef.html</id>
    <link href="https://devilsean.github.io/posts/22b7bef.html"/>
    <published>2026-03-15T12:00:00.000Z</published>
    <summary>从函数模板调用出发，梳理 ParamType 为指针/引用、万能引用和按值传递时的 T 与 ParamType 推导规则，并用示例对比数组与函数实参的退化行为。</summary>
    <title>Item1：理解模板类型推导</title>
    <updated>2026-10-30T11:04:53.774Z</updated>
  </entry>
  <entry>
    <author>
      <name>Sean</name>
    </author>
    <category term="Effective Modern C++" scheme="https://devilsean.github.io/categories/Effective-Modern-C/"/>
    <category term="1. 类别推导" scheme="https://devilsean.github.io/categories/Effective-Modern-C/1-%E7%B1%BB%E5%88%AB%E6%8E%A8%E5%AF%BC/"/>
    <category term="C++" scheme="https://devilsean.github.io/tags/C/"/>
    <category term="类别推导" scheme="https://devilsean.github.io/tags/%E7%B1%BB%E5%88%AB%E6%8E%A8%E5%AF%BC/"/>
    <category term="auto" scheme="https://devilsean.github.io/tags/auto/"/>
    <content>
      <![CDATA[<p>推导方式和[[1. 理解模板类型推导]]一致，下面讨论不同点：</p><p>在初始化时，采用<code>auto</code>声明变量相比于采用固定类型声明变量更具优势。下列声明都能通过编译，但是结果却并不相同：</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">auto</span> x1 = <span class="number">27</span>; <span class="comment">// 类型是int，值是27</span></span><br><span class="line"><span class="function"><span class="keyword">auto</span> <span class="title">x2</span><span class="params">(<span class="number">27</span>)</span></span>; <span class="comment">// 同上</span></span><br><span class="line"><span class="keyword">auto</span> x3 = &#123;<span class="number">27</span>&#125;; <span class="comment">// 类型是std::initializer_list&lt;int&gt;，值是&#123;27&#125;</span></span><br><span class="line"><span class="keyword">auto</span> x4&#123;<span class="number">27</span>&#125;; <span class="comment">//同上 </span></span><br></pre></td></tr></table></figure><p>当用于<code>auto</code>声明变量的初始化表达式是使用大括号时，推导所得类型就属于<code>std::initializer_list&lt;int&gt;</code>。如果类型推导失败（例如大括号里的值类型不一样），代码就不通过编译：</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">auto</span> x5 = &#123;<span class="number">1</span>,<span class="number">2</span>,<span class="number">3.0</span>&#125;; <span class="comment">// 错误，推导不出std::initializer_list&lt;T&gt;中的T</span></span><br></pre></td></tr></table></figure><p>这里发生了两次类型推导：</p><ol><li><code>auto</code>的使用：<code>x5</code>的类型需要推导，由于<code>x5</code>的初始表达式是用大括号，所以<code>x5</code>必须推导为一个<code>std::initializer_list</code>。</li><li><code>std::initializer_list&lt;T&gt;</code>中<code>T</code>的推导：<code>std::initializer_list</code>是个模板，需要根据某个类型<code>T</code>产生类型<code>std::initializer_list&lt;T&gt;</code>，所以<code>T</code>的类型需要被推导出来。</li></ol><p>但是，对于普通模板而言，不会自动识别为<code>std::initializer_list</code>：</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">template</span>&lt;<span class="keyword">typename</span> T&gt; </span></span><br><span class="line"><span class="function"><span class="type">void</span> <span class="title">f</span><span class="params">(T param)</span></span>;</span><br><span class="line"></span><br><span class="line"><span class="built_in">f</span>(&#123; <span class="number">11</span>, <span class="number">23</span>, <span class="number">9</span> &#125;); <span class="comment">// 编译错误！无法推导T的类型</span></span><br></pre></td></tr></table></figure><p>又但是，如果指定模板中的<code>param</code>为<code>std::initializer_list&lt;T&gt;</code>，则在<code>T</code>的类型未知的情况下，模板类型推导机制会推导出<code>T</code>应该的类型：</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">template</span>&lt;<span class="keyword">typename</span> T&gt; </span></span><br><span class="line"><span class="function"><span class="type">void</span> <span class="title">f</span><span class="params">(std::initializer_list&lt;T&gt; initlist)</span></span>; </span><br><span class="line"></span><br><span class="line"><span class="built_in">f</span>(&#123; <span class="number">11</span>, <span class="number">23</span>, <span class="number">9</span> &#125;); <span class="comment">// 编译通过！</span></span><br></pre></td></tr></table></figure><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><ul><li>在一般情况下，<code>auto</code> 类型推导和模板类型推导是一模一样的，但是 <code>auto</code> 类型推导会假定用大括号括起的初始化表达式代表一个 <code>std::initializer_list</code>，但模板类型推导却不会。</li><li>在函数返回值或 <code>lambda</code> 式的形参中使用 <code>auto</code> , 意思是使用模板类型推导而非 <code>auto</code> 类型推导。</li></ul>]]>
    </content>
    <id>https://devilsean.github.io/posts/c58a05ed.html</id>
    <link href="https://devilsean.github.io/posts/c58a05ed.html"/>
    <published>2026-03-15T12:00:00.000Z</published>
    <summary>对比 auto 与模板类型推导的异同，说明花括号初始化列表、initializer_list、代理类等场景下的推导结果与常见陷阱。</summary>
    <title>Item2：理解 auto 类型推导</title>
    <updated>2026-10-30T11:04:53.790Z</updated>
  </entry>
  <entry>
    <author>
      <name>Sean</name>
    </author>
    <category term="Effective Modern C++" scheme="https://devilsean.github.io/categories/Effective-Modern-C/"/>
    <category term="1. 类别推导" scheme="https://devilsean.github.io/categories/Effective-Modern-C/1-%E7%B1%BB%E5%88%AB%E6%8E%A8%E5%AF%BC/"/>
    <category term="C++" scheme="https://devilsean.github.io/tags/C/"/>
    <category term="类别推导" scheme="https://devilsean.github.io/tags/%E7%B1%BB%E5%88%AB%E6%8E%A8%E5%AF%BC/"/>
    <category term="decltype" scheme="https://devilsean.github.io/tags/decltype/"/>
    <content>
      <![CDATA[<p>个人认为原著写的非常晦涩难懂，所以精简如下：</p><p><code>decltype</code>用于告知名字或表达式的类型：</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br></pre></td><td class="code"><pre><span class="line"><span class="type">const</span> <span class="type">int</span> i = <span class="number">0</span>;               <span class="comment">// decltype(i) 是 const int</span></span><br><span class="line"></span><br><span class="line"><span class="function"><span class="type">bool</span> <span class="title">f</span><span class="params">(<span class="type">const</span> Widget&amp; w)</span></span>;       <span class="comment">// decltype(w) 是 const Widget&amp;；decltype(f) 是 bool(const Widget&amp;)</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">struct</span> <span class="title class_">Point</span> &#123;</span><br><span class="line">  <span class="type">int</span> x, y;</span><br><span class="line">&#125;;                             <span class="comment">// decltype(Point::x) 是 int；decltype(Point::y) 是 int</span></span><br><span class="line"></span><br><span class="line">Widget w;                      <span class="comment">// decltype(w) 是 Widget</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">if</span> (<span class="built_in">f</span>(w)) ...                  <span class="comment">// decltype(f(w)) 是 bool</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">template</span>&lt;<span class="keyword">typename</span> T&gt;           <span class="comment">// std::vector 的简化版</span></span><br><span class="line"><span class="keyword">class</span> <span class="title class_">vector</span> &#123;</span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line">  ...</span><br><span class="line">  T&amp; <span class="keyword">operator</span>[](std::<span class="type">size_t</span> index);</span><br><span class="line">  ...</span><br><span class="line">&#125;;</span><br><span class="line"></span><br><span class="line">vector&lt;<span class="type">int</span>&gt; v;</span><br><span class="line">...</span><br><span class="line"><span class="keyword">if</span> (v[<span class="number">0</span>] == <span class="number">0</span>) ...             <span class="comment">// decltype(v[0]) 是 int&amp;</span></span><br></pre></td></tr></table></figure><p><strong>使用场景</strong>：当函数的返回类型依赖于参数类型时：我们不知道用户会传什么容器进来</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// C++11的写法（尾置返回类型）</span></span><br><span class="line"><span class="function"><span class="keyword">template</span>&lt;<span class="keyword">typename</span> Container, <span class="keyword">typename</span> Index&gt;</span></span><br><span class="line"><span class="function"><span class="keyword">auto</span> <span class="title">authAndAccess</span><span class="params">(Container&amp; c, Index i)</span></span></span><br><span class="line"><span class="function">    -&gt; <span class="title">decltype</span><span class="params">(c[i])</span>  <span class="comment">// 告诉编译器：返回类型就是c[i]的类型</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">    <span class="keyword">return</span> c[i];</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// C++14的写法更简洁</span></span><br><span class="line"><span class="function"><span class="keyword">template</span>&lt;<span class="keyword">typename</span> Container, <span class="keyword">typename</span> Index&gt;</span></span><br><span class="line"><span class="function"><span class="keyword">decltype</span>(<span class="keyword">auto</span>) <span class="title">authAndAccess</span><span class="params">(Container&amp; c, Index i)</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">    <span class="keyword">return</span> c[i];  <span class="comment">// 编译器自动推导返回类型</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>我们希望这个函数返回容器元素，类型要和容器<code>[]</code>操作符返回的一致。但不同的容器，<code>operator[]</code>返回的类型可能不同：</p><ul><li><code>vector&lt;int&gt;</code>的<code>operator[]</code>返回<code>int&amp;</code></li><li><code>vector&lt;bool&gt;</code>的<code>operator[]</code>返回一个特殊对象</li></ul><p><strong>和<code>auto</code>的区别</strong>：<code>auto</code>会去掉引用，<code>decltype</code>原样返回表达式的类型（引用&#x2F;<code>const</code>会保留）。</p><p><strong><code>decltype(auto)</code></strong>：这是C++14的特性，意思是：</p><ul><li>用<code>auto</code>来自动类型推导</li><li>但用<code>decltype</code>的规则来推导（保留引用）<br>可以理解为 <strong>保留引用&#x2F;const的auto</strong>。</li></ul><p><strong>一个小陷阱</strong>：</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 括号的微妙影响</span></span><br><span class="line"><span class="type">int</span> x = <span class="number">0</span>;</span><br><span class="line"><span class="keyword">decltype</span>(x) a = x;     <span class="comment">// int</span></span><br><span class="line"><span class="keyword">decltype</span>((x)) b = x;   <span class="comment">// int&amp;</span></span><br></pre></td></tr></table></figure><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><ul><li>绝大多数情况下，<code>decltype</code>会得出变量或表达式的类型而不作任何修改。</li><li>对于类型为 <code>T</code> 的左值表达式，除非该表达式仅有一个名字， <code>decltype</code> 总是得出类型 <code>T&amp;</code> 。</li><li>C++14 支持 <code>decltype(auto)</code> ，和 <code>auto</code> 一样，它会从其初始化表达式出发来。</li></ul>]]>
    </content>
    <id>https://devilsean.github.io/posts/b10f45c0.html</id>
    <link href="https://devilsean.github.io/posts/b10f45c0.html"/>
    <published>2026-03-15T12:00:00.000Z</published>
    <summary>用精简示例说明 decltype 如何保留表达式的引用与 const 限定，并对比 auto，重点覆盖函数返回类型依赖参数类型的场景。</summary>
    <title>Item3：理解 decltype 类型推导</title>
    <updated>2026-10-30T11:04:53.805Z</updated>
  </entry>
  <entry>
    <author>
      <name>Sean</name>
    </author>
    <category term="Effective Modern C++" scheme="https://devilsean.github.io/categories/Effective-Modern-C/"/>
    <category term="1. 类别推导" scheme="https://devilsean.github.io/categories/Effective-Modern-C/1-%E7%B1%BB%E5%88%AB%E6%8E%A8%E5%AF%BC/"/>
    <category term="C++" scheme="https://devilsean.github.io/tags/C/"/>
    <category term="类别推导" scheme="https://devilsean.github.io/tags/%E7%B1%BB%E5%88%AB%E6%8E%A8%E5%AF%BC/"/>
    <content>
      <![CDATA[<p>采用何种工具来查看型别推导结果，取决于在软件开发过程的哪个阶段需要该信息。三个可能的阶段：撰写代码阶段、编译阶段和运行时阶段。</p><h2 id="IDE编辑器"><a href="#IDE编辑器" class="headerlink" title="IDE编辑器"></a>IDE编辑器</h2><p>IDE 中的代码编辑器通常会在你将鼠标指针悬停至某个程序实体，如变量、形参、函数等时，显示出该实体的类型。例如：</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="type">const</span> <span class="type">int</span> theAnswer = <span class="number">42</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">auto</span> x = theAnswer;</span><br><span class="line"><span class="keyword">auto</span> y = &amp;theAnswer;</span><br></pre></td></tr></table></figure><p>IDE 编辑器很可能会显示出，<code>x</code> 的类型推导结果是 <code>int</code>, 而 <code>y</code> 则是 <code>const int*</code>。</p><p>但是如果遇到较为复杂的类型，IDE显示的信息就不太有用了。</p><h2 id="编译器诊断信息"><a href="#编译器诊断信息" class="headerlink" title="编译器诊断信息"></a>编译器诊断信息</h2><p>一条有效途径是使用该类型导致某些编译错误。而报告错误的消息几乎肯定会提及导致该错误的类型。</p><p>例如：</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">template</span>&lt;<span class="keyword">typename</span> T&gt; <span class="comment">//只声明TD而不定义</span></span><br><span class="line"><span class="keyword">class</span> <span class="title class_">TD</span>; <span class="comment">// TD是“类型显示类”的缩写</span></span><br></pre></td></tr></table></figure><p>只要试图具现该模板，就会诱发一个错误信息，原因是找不到具现模板所需要的定义。</p><h2 id="运行时输出"><a href="#运行时输出" class="headerlink" title="运行时输出"></a>运行时输出</h2><p>使用 <code>printf</code>来显示类型信息，这种方法只有到了运行期才能使用，却可以对类型输出的格式提供完全的控制。</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">std::cout &lt;&lt; <span class="built_in">typeid</span>(x).<span class="built_in">name</span>() &lt;&lt; <span class="string">&#x27;\n&#x27;</span>; <span class="comment">//display types for x and y</span></span><br><span class="line">std::cout &lt;&lt; <span class="built_in">typeid</span>(y).<span class="built_in">name</span>() &lt;&lt; <span class="string">&#x27;\n&#x27;</span>; </span><br></pre></td></tr></table></figure><p>但是推导结果并不可靠，根据C++标准，<code>std::type_info::name</code>处理类型的方式类似于按值传递参数，这意味着它会忽略掉引用和顶层的<code>const/volatile</code>修饰符。</p><p>推荐方案：使用 <code>Boost.TypeIndex</code>库<br>例如：</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string">&lt;boost/type_index.hpp&gt;</span></span></span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">template</span>&lt;<span class="keyword">typename</span> T&gt;</span></span><br><span class="line"><span class="function"><span class="type">void</span> <span class="title">f</span><span class="params">(<span class="type">const</span> T&amp; param)</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">    <span class="keyword">using</span> std::cout;</span><br><span class="line">    <span class="keyword">using</span> boost::typeindex::type_id_with_cvr;</span><br><span class="line"></span><br><span class="line">    <span class="comment">//显示T</span></span><br><span class="line">    cout &lt;&lt; <span class="string">&quot;T =     &quot;</span></span><br><span class="line">         &lt;&lt; <span class="built_in">type_id_with_cvr</span>&lt;T&gt;().<span class="built_in">pretty_name</span>()</span><br><span class="line">         &lt;&lt; <span class="string">&#x27;\n&#x27;</span>;</span><br><span class="line">    </span><br><span class="line">    <span class="comment">//显示param类型</span></span><br><span class="line">    cout &lt;&lt; <span class="string">&quot;param = &quot;</span></span><br><span class="line">         &lt;&lt; <span class="built_in">type_id_with_cvr</span>&lt;<span class="keyword">decltype</span>(param)&gt;().<span class="built_in">pretty_name</span>()</span><br><span class="line">         &lt;&lt; <span class="string">&#x27;\n&#x27;</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>它提供的<code>type_id_with_cvr()</code>函数会保留类型的<code>const</code>、<code>volatile</code>和引用修饰符。其成员函数<code>pretty_name</code>产生一个包含人类可读的类型表示的<code>std::string</code>。</p><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><ul><li>利用 IDE 编辑器、编译器错误消息和 Boost.Typelndex 库常常能够查看到推导而得的类型。</li><li>有些工具产生的结果可能会无用，或者不准确。所以，理解 C+＋类型推导规则是必要的。</li></ul>]]>
    </content>
    <id>https://devilsean.github.io/posts/989c0ec7.html</id>
    <link href="https://devilsean.github.io/posts/989c0ec7.html"/>
    <published>2026-03-15T12:00:00.000Z</published>
    <summary>介绍在 IDE、编译错误信息和运行时三个阶段查看类型推导结果的方法，并说明如何借助库设施让编译器主动暴露类型。</summary>
    <title>Item4：掌握查看类型推导结果的方法</title>
    <updated>2026-10-30T11:04:53.815Z</updated>
  </entry>
  <entry>
    <author>
      <name>Sean</name>
    </author>
    <category term="Effective Modern C++" scheme="https://devilsean.github.io/categories/Effective-Modern-C/"/>
    <category term="2. auto" scheme="https://devilsean.github.io/categories/Effective-Modern-C/2-auto/"/>
    <category term="C++" scheme="https://devilsean.github.io/tags/C/"/>
    <category term="auto" scheme="https://devilsean.github.io/tags/auto/"/>
    <content>
      <![CDATA[<p>个人认为原著写的非常难懂，所以精简总结如下：</p><h2 id="auto与代理类的问题"><a href="#auto与代理类的问题" class="headerlink" title="auto与代理类的问题"></a><code>auto</code>与代理类的问题</h2><p>当使用<code>auto</code>进行类型推导时，如果表达式返回的是代理类，<code>auto</code>会推导出代理类型而不是被代理的实际类型，可能导致未定义行为。</p><p>例如：</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="function">std::vector&lt;<span class="type">bool</span>&gt; <span class="title">features</span><span class="params">(<span class="type">const</span> Widget&amp; w)</span></span>;</span><br><span class="line"><span class="type">bool</span> highPriority = <span class="built_in">features</span>(w)[<span class="number">5</span>];  <span class="comment">// 含义是w具有高优先级吗？</span></span><br><span class="line"><span class="built_in">processWidget</span>(w, highPriority);      <span class="comment">// 按照w的优先级来处理</span></span><br></pre></td></tr></table></figure><p>这段代码没有什么问题，但是如果把<code>highPriority</code>从显式类型改成<code>auto</code>：</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">auto</span> highPriority = <span class="built_in">features</span>(w)[<span class="number">5</span>];  <span class="comment">// 危险！推导出std::vector&lt;bool&gt;::reference</span></span><br><span class="line"><span class="built_in">processWidget</span>(w, highPriority);      <span class="comment">// 可能产生未定义行为！</span></span><br></pre></td></tr></table></figure><p>原因：</p><ol><li><code>std::vector&lt;bool&gt;</code>内部使用紧凑结构存储（每个<code>bool</code>占1位）</li><li>内存最小寻址单位是<strong>字节</strong>，单个 “位” 没有独立的内存地址；</li><li><code>operator[]</code>无法返回<code>bool&amp;</code>（不能对单个位取引用），因此返回一个代理类<code>std::vector&lt;bool&gt;::reference</code></li><li><code>auto</code>不会自动把代理类转换成<code>bool</code>，而是直接推导成<code>std::vector&lt;bool&gt;::reference</code>类型。</li></ol><p>因此需要<strong>显式声明</strong>其为<code>bool</code>类型：</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">auto</span> highPriority = <span class="built_in">static_cast</span>&lt;<span class="type">bool</span>&gt;(<span class="built_in">features</span>(w)[<span class="number">5</span>]);  <span class="comment">// 安全！</span></span><br></pre></td></tr></table></figure><h2 id="如何识别出代理类"><a href="#如何识别出代理类" class="headerlink" title="如何识别出代理类"></a>如何识别出代理类</h2><p>头函数或函数签名会反映出其存在：</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">namespace</span> std &#123;</span><br><span class="line">    <span class="keyword">template</span>&lt;<span class="keyword">class</span> <span class="title class_">Allocator</span>&gt;</span><br><span class="line">    <span class="keyword">class</span> <span class="title class_">vector</span>&lt;<span class="type">bool</span>, Allocator&gt; &#123;</span><br><span class="line">    <span class="keyword">public</span>:</span><br><span class="line">        <span class="keyword">class</span> <span class="title class_">reference</span> &#123; ... &#125;;  <span class="comment">// 代理类！</span></span><br><span class="line">        reference <span class="keyword">operator</span>[](size_type n);</span><br><span class="line">        ...</span><br><span class="line">    &#125;;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>非同寻常的返回值类型：如：<code>std::vector&lt;T&gt;::operator[]</code>通常返回<code>T&amp;</code>，如果返回类型不是<code>T&amp;</code>，可能是代理类。</p><p>常见的代理类场景：</p><ul><li><code>std::vector&lt;bool&gt;</code>的相关操作：位无独立地址，无法返回引用。</li><li>表达式模板（用于优化数值计算）：临时对象多，计算效率低。</li><li>某些智能指针的实现：内存访问不安全&#x2F;权限难控。</li><li>延迟求值（lazy evaluation）系统：提前计算可能无效。</li></ul><h2 id="其他应用场景"><a href="#其他应用场景" class="headerlink" title="其他应用场景"></a>其他应用场景</h2><h3 id="1-精度控制"><a href="#1-精度控制" class="headerlink" title="1. 精度控制"></a>1. 精度控制</h3><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="type">double</span> <span class="title">calcEpsilon</span><span class="params">()</span></span>;</span><br><span class="line"><span class="keyword">auto</span> ep = <span class="built_in">static_cast</span>&lt;<span class="type">float</span>&gt;(<span class="built_in">calcEpsilon</span>());  <span class="comment">// 明确表示精度降低</span></span><br></pre></td></tr></table></figure><p>还可以应用于计算随机访问迭代器的容器中的某个元素下标。</p><h3 id="2-类型转换强调"><a href="#2-类型转换强调" class="headerlink" title="2. 类型转换强调"></a><strong>2. 类型转换强调</strong></h3><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="type">double</span> d = <span class="number">0.7</span>;</span><br><span class="line"><span class="type">size_t</span> size = <span class="number">100</span>;</span><br><span class="line"><span class="keyword">auto</span> index = <span class="built_in">static_cast</span>&lt;<span class="type">int</span>&gt;(d * size);  <span class="comment">// 明确表示浮点到整数的转换</span></span><br></pre></td></tr></table></figure><h3 id="3-表达式模板的代理"><a href="#3-表达式模板的代理" class="headerlink" title="3. 表达式模板的代理"></a>3. 表达式模板的代理</h3><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">Matrix m1, m2, m3, m4;</span><br><span class="line"><span class="keyword">auto</span> sum = <span class="built_in">static_cast</span>&lt;Matrix&gt;(m1 + m2 + m3 + m4);  <span class="comment">// 避免代理类型</span></span><br></pre></td></tr></table></figure><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><ul><li>“隐形”的代理类型可以导致<code>auto</code>根据初始化表达式推导出“错误的”类型。</li><li>带显式类型的初始化习惯用法强制<code>auto</code>推导出你想要的类型。</li></ul>]]>
    </content>
    <id>https://devilsean.github.io/posts/748f5f56.html</id>
    <link href="https://devilsean.github.io/posts/748f5f56.html"/>
    <published>2026-03-15T12:00:00.000Z</published>
    <summary>
      <![CDATA[以 std::vector<bool> 为例说明 auto 推导出代理类带来的悬空引用风险，并给出显式类型初始化习惯用法。]]>
    </summary>
    <title>Item6：当auto推导的类型不符合要求时，使用显式类型初始化习惯用法</title>
    <updated>2026-10-30T11:04:53.842Z</updated>
  </entry>
  <entry>
    <author>
      <name>Sean</name>
    </author>
    <category term="Effective Modern C++" scheme="https://devilsean.github.io/categories/Effective-Modern-C/"/>
    <category term="2. auto" scheme="https://devilsean.github.io/categories/Effective-Modern-C/2-auto/"/>
    <category term="C++" scheme="https://devilsean.github.io/tags/C/"/>
    <category term="auto" scheme="https://devilsean.github.io/tags/auto/"/>
    <content>
      <![CDATA[<h2 id="传统显示类型声明存在的问题"><a href="#传统显示类型声明存在的问题" class="headerlink" title="传统显示类型声明存在的问题"></a>传统显示类型声明存在的问题</h2><h3 id="1-忘记初始化产生未知行为"><a href="#1-忘记初始化产生未知行为" class="headerlink" title="1. 忘记初始化产生未知行为"></a>1. 忘记初始化产生未知行为</h3><p>但是，<code>auto</code>声明的变量其类型推导自其初始化表达式，所以它们必须初始化：</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="type">int</span> x1; <span class="comment">// 有潜在的未初始化风险 </span></span><br><span class="line"><span class="keyword">auto</span> x2; <span class="comment">// 编译错误！必须有初始化表达式</span></span><br><span class="line"><span class="keyword">auto</span> x3 = <span class="number">0</span>; <span class="comment">// 没问题， x 已经定义了</span></span><br></pre></td></tr></table></figure><h3 id="2-类型冗长复杂，难以直接写出"><a href="#2-类型冗长复杂，难以直接写出" class="headerlink" title="2. 类型冗长复杂，难以直接写出"></a>2. 类型冗长复杂，难以直接写出</h3><p>在泛型编程、容器操作等场景中，尝尝会出现冗长复杂的类型声明。如：<code>typename std::iterator_traits&lt;It&gt;::value_type</code>，可以直接使用<code>auto</code>代替。</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 传统显式声明：冗长繁琐，易出错 typename </span></span><br><span class="line">std::vector&lt;<span class="type">int</span>&gt;::const_iterator it1 = vec.<span class="built_in">cbegin</span>(); </span><br><span class="line"></span><br><span class="line"><span class="comment">// auto 声明：简洁高效，无需记忆复杂类型 </span></span><br><span class="line"><span class="keyword">auto</span> it2 = vec.<span class="built_in">cbegin</span>();</span><br></pre></td></tr></table></figure><h3 id="3-类型不匹配导致意外的类型转换"><a href="#3-类型不匹配导致意外的类型转换" class="headerlink" title="3. 类型不匹配导致意外的类型转换"></a>3. 类型不匹配导致意外的类型转换</h3><p><code>std::function</code>是C++11标准库中的一个模板，它把函数指针的思想加以推广。函数指针只能指向函数，而<code>std::function</code>却可以指向任何可调用对象，即任何可以像函数一样实施调用之物。可以处理C++中的闭包类型。</p><p>但是，闭包类型只有编译器自己知道，显式声明闭包类型可能产生潜在的类型转换。如：</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">std::unordered_map&lt;std::string, <span class="type">int</span>&gt; m; </span><br><span class="line"></span><br><span class="line"><span class="comment">// 看似合理，实则存在类型不匹配</span></span><br><span class="line"><span class="keyword">for</span> (<span class="type">const</span> std::pair&lt;std::string, <span class="type">int</span>&gt;&amp; p: m) &#123;</span><br><span class="line"> ... <span class="comment">// 在p上实施某些操作</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>哈希表中的 <code>std::pair</code> （也就是 <code>std::unordered_map</code> 本身）的类型并不是 <code>std::pair&lt;std::string, int&gt;</code> ，而是 <code>std:: pair&lt;const std::string, int&gt;</code>。</p><p>还可以避免一种被称作者为“类型捷径”的问题。如：</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">std::vector&lt;<span class="type">int</span>&gt; v; </span><br><span class="line"><span class="type">unsigned</span> sz = v.<span class="built_in">size</span>();</span><br></pre></td></tr></table></figure><p>在 32 位 Windows 上， <code>unsigned</code> 和 <code>std:: vector&lt;int&gt;::size_type</code> 的尺寸是一样的，但在 64 位 Windows 上， <code>unsigned</code> 是 32 位，而 <code>std:: vector&lt;int&gt;::size_type</code> 则是 64 位。</p><h2 id="其他优势"><a href="#其他优势" class="headerlink" title="其他优势"></a>其他优势</h2><ul><li><code>auto</code>存储所需的内存可能比<code>std::function</code>更小，且调用效率更高（<code>std::function</code>采用类型擦除技术，存在间接调用开销，<code>auto</code>直接推导闭包原始类型，无额外损耗）。</li><li>方便进行统一修改和重构，变量类型会随初始化表达式自动适配，修改源头类型时，无需手动调整所有相关变量，减少遗漏风险。</li><li>支持匿名类型的直接使用，C++中的<code>lambda</code>表达式、匿名类均为编译器生成的匿名类型，无法手动显式声明，<code>auto</code>是唯一能直接存储这类对象的方式。</li></ul><p>可能存在的问题：无法一眼看出对象的类型，但是IDE的对象类型显示能力能缓和这个问题，而且良好的命名比类型更能表达意图。</p><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><ul><li><code>auto</code>变量必须初始化，基本上对会导致兼容性和效率问题的类型不匹配现象免疫，还可以简化重构流程，通常也比显式指定类型要少打一些字。</li><li><code>auto</code>类型的变量都有着2和6中所描述的毛病。</li></ul>]]>
    </content>
    <id>https://devilsean.github.io/posts/a8adcefc.html</id>
    <link href="https://devilsean.github.io/posts/a8adcefc.html"/>
    <published>2026-03-15T12:00:00.000Z</published>
    <summary>从避免未初始化、简化冗长类型和防止意外转换三个角度，说明现代 C++ 中优先使用 auto 的理由与适用边界。</summary>
    <title>Item5：优先使用 auto，而非显式类型声明</title>
    <updated>2026-10-30T11:04:53.828Z</updated>
  </entry>
  <entry>
    <author>
      <name>Sean</name>
    </author>
    <category term="Effective Modern C++" scheme="https://devilsean.github.io/categories/Effective-Modern-C/"/>
    <category term="4. 智能指针" scheme="https://devilsean.github.io/categories/Effective-Modern-C/4-%E6%99%BA%E8%83%BD%E6%8C%87%E9%92%88/"/>
    <category term="C++" scheme="https://devilsean.github.io/tags/C/"/>
    <category term="unique_ptr" scheme="https://devilsean.github.io/tags/unique-ptr/"/>
    <content>
      <![CDATA[<h2 id="std-unique-ptr-的特性"><a href="#std-unique-ptr-的特性" class="headerlink" title="std::unique_ptr 的特性"></a><code>std::unique_ptr</code> 的特性</h2><ol><li><strong>轻量高效</strong>：默认删除器下，大小和原始指针一致，操作（如解引用）的执行效率和原始指针几乎无差别，可用于内存 &#x2F; 性能敏感场景。</li><li><strong>独占所有权</strong>：一个非空的 <code>std::unique_ptr</code> 独占指向的资源，<strong>仅可移动（move-only）、不可拷贝</strong>—— 拷贝会导致两个指针同时管理同一资源，析构时重复销毁；移动则会将所有权从源指针转移到目标指针（源指针置空）。</li><li><strong>自动析构</strong>：析构时会自动销毁指向的资源（默认调用 <code>delete</code>），即便程序因异常、提前返回等非典型流程终止（除异常传播到 main 外、调用<code>abort</code>&#x2F;<code>exit</code>等极端情况），也能保证资源释放，避免内存泄漏。</li></ol><h2 id="应用场景"><a href="#应用场景" class="headerlink" title="应用场景"></a>应用场景</h2><ol><li><p><strong>工厂函数返回值</strong>：</p><p> 适合作为继承体系对象的工厂函数返回类型（如 Investment 基类 + Stock&#x2F;Bond 派生类），调用方独占工厂创建的堆对象所有权，无需手动管理销毁，示例中通过<code>makeInvestment</code>返回<code>unique_ptr&lt;Investment&gt;</code>，自动释放派生类对象。</p></li><li><p><strong>接管 C 风格 API 的堆数组</strong>：</p><p> <code>std::unique_ptr</code> 有数组版本 <code>std::unique_ptr&lt;T[]&gt;</code>（仅适配 C 风格堆数组），但优先推荐<code>std::array</code>&#x2F;<code>std::vector</code>等容器，仅在必须接管原始数组时使用。</p></li></ol><h2 id="自定义删除器"><a href="#自定义删除器" class="headerlink" title="自定义删除器"></a>自定义删除器</h2><ol><li><p><strong>使用方式</strong>：可自定义销毁逻辑（如先打日志再<code>delete</code>），删除器可以是 lambda、函数对象或普通函数，需将删除器类型作为<code>std::unique_ptr</code>的第二个模板参数。</p></li><li><p><strong>注意事项</strong>：</p><ul><li>管理继承体系对象时，基类必须声明<strong>虚析构函数</strong>，否则通过基类指针删除派生类对象会未定义行为；</li><li>删除器影响<code>unique_ptr</code>大小：无状态 lambda（不捕获变量）无额外开销，函数指针 &#x2F; 有状态删除器会增加其大小（通常从 1 个指针大小变为 2 个），优先用 lambda 实现自定义删除器。</li></ul></li><li><p>C++14 优化：支持函数返回类型推导，可将删除器定义在工厂函数内部，代码更简洁封装。</p></li></ol><h2 id="特殊形式与兼容性"><a href="#特殊形式与兼容性" class="headerlink" title="特殊形式与兼容性"></a>特殊形式与兼容性</h2><ol><li><p><strong>两种形式区分</strong>：</p><ul><li><code>std::unique_ptr&lt;T&gt;</code>：管理单个对象，支持<code>*</code>&#x2F;<code>-&gt;</code>解引用；</li><li><code>std::unique_ptr&lt;T[]&gt;</code>：管理数组，支持<code>[]</code>下标访问；</li></ul></li><li><p><strong>无缝转换为<code>std::shared_ptr</code></strong>：</p><p> <code>std::unique_ptr</code> 可直接赋值给<code>std::shared_ptr</code>，工厂函数返回<code>unique_ptr</code>既保证调用方的高效独占使用，也不限制调用方转为共享所有权（<code>shared_ptr</code>），是工厂函数返回类型的最优选择。</p></li></ol><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><ol><li><code>std::unique_ptr</code> 是轻量、高效的 “独占所有权” 智能指针，仅可移动，析构自动释放资源；</li><li>自定义删除器灵活，但无状态 lambda 开销最小，管理继承对象需基类虚析构；</li><li>核心优势是适配工厂函数场景，且可无缝转为<code>std::shared_ptr</code>，兼顾独占 &#x2F; 共享所有权需求。</li></ol>]]>
    </content>
    <id>https://devilsean.github.io/posts/5763250.html</id>
    <link href="https://devilsean.github.io/posts/5763250.html"/>
    <published>2026-03-15T12:00:00.000Z</published>
    <summary>介绍 unique_ptr 的独占所有权、零开销和只移动语义，说明如何用自定义删除器管理非内存资源。</summary>
    <title>Item18：使用独占指针管理具备专属所有权的资源</title>
    <updated>2026-10-30T11:04:54.031Z</updated>
  </entry>
  <entry>
    <author>
      <name>Sean</name>
    </author>
    <category term="Effective Modern C++" scheme="https://devilsean.github.io/categories/Effective-Modern-C/"/>
    <category term="4. 智能指针" scheme="https://devilsean.github.io/categories/Effective-Modern-C/4-%E6%99%BA%E8%83%BD%E6%8C%87%E9%92%88/"/>
    <category term="C++" scheme="https://devilsean.github.io/tags/C/"/>
    <category term="shared_ptr" scheme="https://devilsean.github.io/tags/shared-ptr/"/>
    <content>
      <![CDATA[<h2 id="定位与实现原理"><a href="#定位与实现原理" class="headerlink" title="定位与实现原理"></a>定位与实现原理</h2><ol><li><p><strong>功能</strong>：管理<strong>共享所有权</strong>的资源，兼具 “自动垃圾回收”（无需手动管理生命周期）和 “销毁可预测”（最后一个指向资源的 <code>shared_ptr</code> 销毁时，资源确定销毁）的优势，比纯垃圾回收语言的资源管理更通用、销毁时机更可控。</p></li><li><p><strong>实现基础</strong>：引用计数（跟踪指向资源的 <code>shared_ptr</code> 数量）：</p><ul><li>拷贝 &#x2F; 构造：递增引用计数；</li><li>析构 &#x2F; 赋值指向其他对象：递减引用计数；计数归 0 时销毁资源；</li><li>移动更高效：移动 <code>shared_ptr</code> 无需修改引用计数（源指针置空），比拷贝快。</li></ul></li><li><p><strong>性能开销</strong>（对比原始指针 &#x2F;<code>unique_ptr</code>）：</p><ul><li>大小：原始指针的 2 倍（存储 “指向资源的指针”+“指向引用计数的指针”）；</li><li>额外成本：引用计数需动态分配（<code>make_shared</code> 可优化）、引用计数修改是原子操作（线程安全但比非原子操作慢）。</li></ul></li></ol><h2 id="自定义删除器"><a href="#自定义删除器" class="headerlink" title="自定义删除器"></a>自定义删除器</h2><p>差异：删除器类型<strong>不是</strong> <code>shared_ptr</code> 类型的一部分（<code>unique_ptr</code> 的删除器是类型一部分）；</p><ul><li>优势：不同删除器的同类型 <code>shared_ptr</code>（如 <code>shared_ptr&lt;Widget&gt;</code>）可放入同一容器、相互赋值、传入同一函数；</li><li>大小不变：删除器存储在 “控制块” 中，而非 <code>shared_ptr</code> 对象本身，因此无论删除器是何类型，<code>shared_ptr</code> 始终是 2 个指针大小。</li></ul><h2 id="控制块"><a href="#控制块" class="headerlink" title="控制块"></a>控制块</h2><ol><li><p>定义：每个被 <code>shared_ptr</code> 管理的资源对应一个控制块，除引用计数外，还存储自定义删除器 &#x2F; 分配器等；</p></li><li><p><strong>创建规则</strong>（错用会导致未定义行为）：</p><ul><li><code>make_shared</code>、从 <code>unique_ptr</code>&#x2F; 原始指针构造：创建新控制块；</li><li>从 <code>shared_ptr</code>&#x2F;<code>weak_ptr</code> 构造：复用已有控制块（不新建）；</li></ul></li><li><p>致命风险：从<strong>同一原始指针</strong>创建多个 <code>shared_ptr</code> → 生成多个控制块 → 资源被多次销毁；</p><ul><li>规避：① 直接传 <code>new</code> 的结果给 <code>shared_ptr</code> 构造函数（而非原始指针变量）；② 用已有 <code>shared_ptr</code> 拷贝构造新对象（复用控制块）。</li></ul></li></ol><h2 id="this-指针的安全使用（enable-shared-from-this）"><a href="#this-指针的安全使用（enable-shared-from-this）" class="headerlink" title="this 指针的安全使用（enable_shared_from_this）"></a><code>this</code> 指针的安全使用（<code>enable_shared_from_this</code>）</h2><ol><li>问题：成员函数中直接传 <code>this</code>（原始指针）给 <code>shared_ptr</code> → 可能为同一对象创建新控制块（若外部已有指向该对象的 <code>shared_ptr</code>）；</li><li>解决方案：让类继承 <code>std::enable_shared_from_this&lt;类名&gt;</code>（CRTP 模式），调用 <code>shared_from_this()</code> 替代 <code>this</code>（复用已有控制块）；</li><li>前提：调用 <code>shared_from_this()</code> 前，已有指向该对象的 <code>shared_ptr</code>（否则行为未定义）；最佳实践是类的构造函数私有化，通过工厂函数创建 <code>shared_ptr</code> 对象。</li></ol><h2 id="局限性与选型建议"><a href="#局限性与选型建议" class="headerlink" title="局限性与选型建议"></a>局限性与选型建议</h2><ol><li>不支持数组（C++17 前）：API 为单个对象设计（无 <code>operator[]</code>），强行管理数组是糟糕设计，优先用 <code>std::array</code>&#x2F;<code>vector</code>；</li><li>所有权不可变更：一旦资源由 <code>shared_ptr</code> 管理，即使引用计数为 1，也无法转为 <code>unique_ptr</code>；</li><li>选型原则：能独占则优先用 <code>unique_ptr</code>（更高效），<code>unique_ptr</code> 可无缝转为 <code>shared_ptr</code>；仅确需共享所有权时，才用 <code>shared_ptr</code>。</li></ol><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><ol><li><code>std::shared_ptr</code> 是共享所有权资源的 “自动垃圾回收” 方案，核心靠引用计数 + 控制块实现，但有大小、原子操作等性能开销；</li><li>自定义删除器不影响 <code>shared_ptr</code> 类型，这是其比 <code>unique_ptr</code> 灵活的关键，但删除器存储在控制块中；</li><li>避坑点：禁止从同一原始指针创建多个 <code>shared_ptr</code>，成员函数中用 <code>shared_from_this()</code> 替代直接传 <code>this</code>；</li><li>优先用 <code>unique_ptr</code> 管理独占资源，仅共享时用 <code>shared_ptr</code>。</li></ol>]]>
    </content>
    <id>https://devilsean.github.io/posts/3151d289.html</id>
    <link href="https://devilsean.github.io/posts/3151d289.html"/>
    <published>2026-03-15T12:00:00.000Z</published>
    <summary>解释 shared_ptr 的共享所有权、引用计数和控制块机制，并讨论拷贝、移动与析构的成本。</summary>
    <title>Item19：对于共享资源使用共享指针</title>
    <updated>2026-10-30T11:04:54.049Z</updated>
  </entry>
  <entry>
    <author>
      <name>Sean</name>
    </author>
    <category term="Effective Modern C++" scheme="https://devilsean.github.io/categories/Effective-Modern-C/"/>
    <category term="3. 转向现代C++" scheme="https://devilsean.github.io/categories/Effective-Modern-C/3-%E8%BD%AC%E5%90%91%E7%8E%B0%E4%BB%A3C/"/>
    <category term="C++" scheme="https://devilsean.github.io/tags/C/"/>
    <category term="enum class" scheme="https://devilsean.github.io/tags/enum-class/"/>
    <content>
      <![CDATA[<h2 id="使用限域enum来减少命名空间污染"><a href="#使用限域enum来减少命名空间污染" class="headerlink" title="使用限域enum来减少命名空间污染"></a>使用限域<code>enum</code>来减少命名空间污染</h2><p>通用规则：如果在一对大括号里声明一个名字，则该名字的可见性限定在括号括起来的作用域内。</p><p>但这个规则不适用于 C++98 风格的枚举类型中定义的枚举变量：枚举变量的属于包含着这个枚举类型的作用域，此作用域内不能有其他实体取相同的名字。</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">enum</span> <span class="title class_">Color</span> &#123; black, white, red &#125;; <span class="comment">//black, white, red在Color所在的作用域</span></span><br><span class="line"><span class="keyword">auto</span> white = <span class="literal">false</span>; <span class="comment">//错误! white早已在这个作用域中声明</span></span><br></pre></td></tr></table></figure><p>枚举量的名字泄露到枚举类型所在作用域的现象被称为：不限范围的枚举类型（未限域 enum）。</p><p>在 C++11 中，限定作用域的枚举类型（枚举类 &#x2F; 限域 enum）不会以这样的方式泄露名字：</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">enum class</span> <span class="title class_">Color</span> &#123; black, white, red &#125;; <span class="comment">//black, white, red限制在Color域内</span></span><br><span class="line"><span class="keyword">auto</span> white = <span class="literal">false</span>; <span class="comment">//没问题，域内没有其他“white”</span></span><br><span class="line"></span><br><span class="line">Color c = white; <span class="comment">//错误，域中没有枚举名叫white</span></span><br><span class="line">Color c = Color::white; <span class="comment">//没问题</span></span><br><span class="line"><span class="keyword">auto</span> c = Color::white; <span class="comment">//也没问题（也符合Item5的建议）</span></span><br></pre></td></tr></table></figure><h2 id="限域枚举的枚举量是强类型的"><a href="#限域枚举的枚举量是强类型的" class="headerlink" title="限域枚举的枚举量是强类型的"></a>限域枚举的枚举量是强类型的</h2><blockquote><p>强类型：语言会对类型兼容性做严格检查，禁止隐式的、不安全的类型转换，每个值都有明确且固定的类型，类型错误会被尽可能早地捕获（编译器 &#x2F; 运行期）。</p></blockquote><p>不限域枚举类型的枚举量可以隐式转换为整数类型（并能够从此处进一步转换到浮点类型），导致语义扭曲的代码合法：</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">enum</span> <span class="title class_">Color</span> &#123; black, white, red &#125;; <span class="comment">//未限域enum</span></span><br><span class="line"></span><br><span class="line">std::vector&lt;std::<span class="type">size_t</span>&gt;<span class="built_in">primeFactors</span>(std::<span class="type">size_t</span> x); <span class="comment">//func返回x的质因子</span></span><br><span class="line"></span><br><span class="line">Color c = red;</span><br><span class="line"><span class="keyword">if</span> (c &lt; <span class="number">14.5</span>) &#123; <span class="comment">// Color与double比较 (!)</span></span><br><span class="line">    <span class="keyword">auto</span> factors = <span class="built_in">primeFactors</span>(c); <span class="comment">// 向std::size_t参数传递Color (!)</span></span><br><span class="line">    …</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>从限定作用域的枚举类型到任何其他类型都不存在隐式转换路径，类型安全更优：</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">enum class</span> <span class="title class_">Color</span> &#123; black, white, red &#125;; <span class="comment">//Color现在是限域enum</span></span><br><span class="line"></span><br><span class="line">Color c = Color::red;                   </span><br><span class="line"><span class="keyword">if</span> (c &lt; <span class="number">14.5</span>) &#123;                         <span class="comment">//错误！不能比较Color和double</span></span><br><span class="line">    <span class="keyword">auto</span> factors = <span class="built_in">primeFactors</span>(c);     <span class="comment">//错误！不能向std::size_t参数传Color</span></span><br><span class="line">    …</span><br><span class="line">&#125;</span><br><span class="line"><span class="comment">// 仅显式强制转换才允许（可控的类型转换）</span></span><br><span class="line"><span class="keyword">if</span> (<span class="built_in">static_cast</span>&lt;<span class="type">double</span>&gt;(c) &lt; <span class="number">14.5</span>) &#123;</span><br><span class="line">    <span class="keyword">auto</span> factors = <span class="built_in">primeFactors</span>(<span class="built_in">static_cast</span>&lt;std::<span class="type">size_t</span>&gt;(c));</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="限定作用域的枚举类型可以进行前置声明"><a href="#限定作用域的枚举类型可以进行前置声明" class="headerlink" title="限定作用域的枚举类型可以进行前置声明"></a>限定作用域的枚举类型可以进行前置声明</h2><p>前置声明的核心前提是<strong>编译器能确定枚举的底层类型（内存大小）</strong>：</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">enum</span> <span class="title class_">Color</span>; <span class="comment">// 错误！未限域enum无默认底层类型，编译器无法推断内存大小</span></span><br><span class="line"><span class="keyword">enum class</span> <span class="title class_">Color</span>; <span class="comment">// 没问题！限域enum默认底层类型为int，内存大小确定</span></span><br></pre></td></tr></table></figure><h3 id="底层类型与前置声明的完整规则"><a href="#底层类型与前置声明的完整规则" class="headerlink" title="底层类型与前置声明的完整规则"></a>底层类型与前置声明的完整规则</h3><ol><li>所有 enum 的底层类型是编译器为优化内存选择的整型（需覆盖所有枚举值的最小类型）；</li><li>限域 enum：默认底层类型为<code>int</code>（也可显式指定），因此<strong>总能直接前置声明</strong>：</li></ol><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">enum class</span> <span class="title class_">Status</span>; <span class="comment">// 默认int，合法</span></span><br><span class="line"><span class="keyword">enum class</span> <span class="title class_">Status</span>: std::<span class="type">uint32_t</span>; <span class="comment">// 显式指定底层类型，合法</span></span><br></pre></td></tr></table></figure><ol><li>未限域 enum：无默认底层类型，<strong>仅显式指定底层类型时才能前置声明</strong>：</li></ol><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">enum</span> <span class="title class_">Color</span>: std::<span class="type">uint8_t</span>; <span class="comment">// 显式指定底层类型，合法</span></span><br></pre></td></tr></table></figure><ol><li>两者均可在定义时显式指定底层类型：</li></ol><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">enum class</span> <span class="title class_">Status</span>: std::<span class="type">uint32_t</span> &#123; good = <span class="number">0</span>, failed = <span class="number">1</span> &#125;;</span><br><span class="line"><span class="keyword">enum</span> <span class="title class_">Color</span>: std::<span class="type">uint8_t</span> &#123; black, white, red &#125;;</span><br></pre></td></tr></table></figure><h2 id="未限域枚举的唯一实用场景：简化-std-tuple-字段访问"><a href="#未限域枚举的唯一实用场景：简化-std-tuple-字段访问" class="headerlink" title="未限域枚举的唯一实用场景：简化 std::tuple 字段访问"></a>未限域枚举的唯一实用场景：简化 std::tuple 字段访问</h2><p>限域 enum 虽类型安全，但访问<code>std::tuple</code>字段时因无隐式转换，代码冗长；未限域 enum 可利用 “隐式转换为 std::size_t” 简化模板实参传递：</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 定义tuple类型（存储用户名、邮箱、声望值）</span></span><br><span class="line"><span class="keyword">using</span> UserInfo = std::tuple&lt;std::string, std::string, std::<span class="type">size_t</span>&gt;;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 未限域enum：简洁访问tuple字段（依赖隐式转换为std::size_t）</span></span><br><span class="line"><span class="keyword">enum</span> <span class="title class_">UserInfoFields</span> &#123; uiName, uiEmail, uiReputation &#125;;</span><br><span class="line">UserInfo uInfo;</span><br><span class="line"><span class="keyword">auto</span> val = std::<span class="built_in">get</span>&lt;uiEmail&gt;(uInfo); <span class="comment">// 直接用枚举成员作为模板实参</span></span><br><span class="line"></span><br><span class="line"><span class="comment">// 限域enum：无隐式转换，代码冗长</span></span><br><span class="line"><span class="keyword">enum class</span> <span class="title class_">UserInfoFields</span> &#123; uiName, uiEmail, uiReputation &#125;;</span><br><span class="line"><span class="keyword">auto</span> val = std::get&lt;<span class="built_in">static_cast</span>&lt;std::<span class="type">size_t</span>&gt;(UserInfoFields::uiEmail)&gt;(uInfo);</span><br></pre></td></tr></table></figure><h2 id="限域枚举访问-tuple-的优化方案：toUType-constexpr-函数模板"><a href="#限域枚举访问-tuple-的优化方案：toUType-constexpr-函数模板" class="headerlink" title="限域枚举访问 tuple 的优化方案：toUType constexpr 函数模板"></a>限域枚举访问 tuple 的优化方案：toUType constexpr 函数模板</h2><p>为兼顾限域 enum 的类型安全与 tuple 访问的简洁性，可实现<strong>编译期生效</strong>的<code>toUType</code>函数模板（转换枚举值为其底层类型）：</p><p>C++11 版本：</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">template</span>&lt;<span class="keyword">typename</span> E&gt;</span><br><span class="line"><span class="keyword">constexpr</span> <span class="keyword">typename</span> std::underlying_type&lt;E&gt;::<span class="function">type</span></span><br><span class="line"><span class="function">    <span class="title">toUType</span><span class="params">(E enumerator)</span> <span class="keyword">noexcept</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">    <span class="keyword">return</span> <span class="keyword">static_cast</span>&lt;<span class="keyword">typename</span> std::underlying_type&lt;E&gt;::type&gt;(enumerator);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>C++14 简化版本（更简洁）：</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">template</span>&lt;<span class="keyword">typename</span> E&gt;</span></span><br><span class="line"><span class="function"><span class="keyword">constexpr</span> <span class="keyword">auto</span> <span class="title">toUType</span><span class="params">(E enumerator)</span> <span class="keyword">noexcept</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">    <span class="keyword">return</span> <span class="keyword">static_cast</span>&lt;std::<span class="type">underlying_type_t</span>&lt;E&gt;&gt;(enumerator);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>调用方式（兼顾安全与简洁）：</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">auto</span> val = std::<span class="built_in">get</span>&lt;<span class="built_in">toUType</span>(UserInfoFields::uiEmail)&gt;(uInfo);</span><br></pre></td></tr></table></figure><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><ul><li>C++98风格的枚举类型，现在称为不限范围的枚举类型。</li><li>限定作用域的枚举类型仅在枚举类型内可见。它们只能通过强制类型转换以转换至其他类型。</li><li>限定作用域的枚举类型和不限范围的枚举类型都支持底层类型指定。限定作用域的枚举类型的默认底层类型是<code>int</code>，而不限范围的枚举类型没有默认底层类型。</li><li>限定作用域的枚举类型总是可以进行前置声明，而不限范围的枚举类型却只有在指定了默认底层类型的前提下才可以进行前置声明。</li></ul>]]>
    </content>
    <id>https://devilsean.github.io/posts/604f8623.html</id>
    <link href="https://devilsean.github.io/posts/604f8623.html"/>
    <published>2026-03-15T12:00:00.000Z</published>
    <summary>解释限域 enum 如何避免枚举名泄漏到外层作用域，并对比不限域枚举在强类型、底层类型和前置声明上的差异。</summary>
    <title>Item10：优先选用限域枚举，而非不限域枚举</title>
    <updated>2026-10-30T11:04:53.857Z</updated>
  </entry>
  <entry>
    <author>
      <name>Sean</name>
    </author>
    <category term="Effective Modern C++" scheme="https://devilsean.github.io/categories/Effective-Modern-C/"/>
    <category term="4. 智能指针" scheme="https://devilsean.github.io/categories/Effective-Modern-C/4-%E6%99%BA%E8%83%BD%E6%8C%87%E9%92%88/"/>
    <category term="C++" scheme="https://devilsean.github.io/tags/C/"/>
    <category term="make_unique" scheme="https://devilsean.github.io/tags/make-unique/"/>
    <category term="make_shared" scheme="https://devilsean.github.io/tags/make-shared/"/>
    <content>
      <![CDATA[<ol><li><code>std::make_shared</code>是 C++11 标准的，而<code>std::make_unique</code>直到 C++14 才加入；</li><li>如果用 C++11，可手写简易版<code>make_unique</code>（仅支持基础场景，不支持数组 &#x2F; 自定义析构），但注意不要放到<code>std</code>命名空间，避免和 C++14 标准库冲突；</li><li>三个 make 函数：<code>make_unique</code>、<code>make_shared</code>、<code>allocate_shared</code>（<code>allocate_shared</code>和<code>make_shared</code>行为一致，只是支持自定义内存分配器），它们的共性是 “接收任意参数→完美转发给对象构造函数→动态分配对象→返回对应智能指针”。</li></ol><h2 id="优先用-make-函数"><a href="#优先用-make-函数" class="headerlink" title="优先用 make 函数"></a>优先用 make 函数</h2><h4 id="避免类型重复"><a href="#避免类型重复" class="headerlink" title="避免类型重复"></a>避免类型重复</h4><p>直接用<code>new</code>创建智能指针时，类型会写两次，而 make 函数只需写一次，符合 “避免重复代码” 的软件工程原则：</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 重复写Widget：不够简洁</span></span><br><span class="line"><span class="function">std::unique_ptr&lt;Widget&gt; <span class="title">upw2</span><span class="params">(<span class="keyword">new</span> Widget)</span></span>;</span><br><span class="line"><span class="function">std::shared_ptr&lt;Widget&gt; <span class="title">spw2</span><span class="params">(<span class="keyword">new</span> Widget)</span></span>;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 只写一次Widget：更简洁</span></span><br><span class="line"><span class="function"><span class="keyword">auto</span> <span class="title">upw1</span><span class="params">(std::make_unique&lt;Widget&gt;())</span></span>;</span><br><span class="line"><span class="function"><span class="keyword">auto</span> <span class="title">spw1</span><span class="params">(std::make_shared&lt;Widget&gt;())</span></span>;</span><br></pre></td></tr></table></figure><h4 id="避免内存泄漏"><a href="#避免内存泄漏" class="headerlink" title="避免内存泄漏"></a>避免内存泄漏</h4><p>这是最关键的优势。直接用<code>new</code>时，编译器可能调整代码执行顺序，导致动态分配的对象因异常泄漏；而 make 函数能保证对象被智能指针及时接管。</p><p><strong>反例（有泄漏风险）</strong>：</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 危险：如果computePriority()在new Widget后、shared_ptr构造前抛异常，Widget会泄漏</span></span><br><span class="line"><span class="built_in">processWidget</span>(std::<span class="built_in">shared_ptr</span>&lt;Widget&gt;(<span class="keyword">new</span> Widget), <span class="built_in">computePriority</span>());</span><br></pre></td></tr></table></figure><p>执行顺序可能是：<code>new Widget</code> → <code>computePriority()</code>（抛异常） → <code>shared_ptr构造</code>（永远执行不到），此时<code>new</code>出的 Widget 无智能指针管理，直接泄漏。</p><p><strong>正例（无泄漏风险）</strong>：</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 安全：make_shared先创建对象并封装到shared_ptr，再调用computePriority()</span></span><br><span class="line"><span class="built_in">processWidget</span>(std::<span class="built_in">make_shared</span>&lt;Widget&gt;(), <span class="built_in">computePriority</span>());</span><br></pre></td></tr></table></figure><ul><li>若<code>make_shared</code>先执行：对象已被<code>shared_ptr</code>接管，即使<code>computePriority()</code>抛异常，<code>shared_ptr</code>析构会释放对象；</li><li>若<code>computePriority()</code>先执行并抛异常：<code>make_shared</code>根本不会调用，无对象分配，自然无泄漏。</li></ul><p>这个逻辑对<code>make_unique</code>&#x2F;<code>unique_ptr</code>同样适用。</p><h4 id="效率更高（仅针对make-shared-allocate-shared）"><a href="#效率更高（仅针对make-shared-allocate-shared）" class="headerlink" title="效率更高（仅针对make_shared&#x2F;allocate_shared）"></a>效率更高（仅针对<code>make_shared</code>&#x2F;<code>allocate_shared</code>）</h4><p>直接用<code>new</code>创建<code>shared_ptr</code>会触发<strong>两次内存分配</strong>：一次给对象本身，一次给<code>shared_ptr</code>的控制块（存储引用计数等信息）；</p><p>而<code>make_shared</code>只需<strong>一次内存分配</strong>：将对象和控制块放在同一块内存中，减少了内存分配次数，代码更小、运行更快，还能减少内存占用。</p><h2 id="不适合使用-make-函数的场景"><a href="#不适合使用-make-函数的场景" class="headerlink" title="不适合使用 make 函数的场景"></a>不适合使用 make 函数的场景</h2><h4 id="需要指定自定义删除器"><a href="#需要指定自定义删除器" class="headerlink" title="需要指定自定义删除器"></a>需要指定自定义删除器</h4><p>make 函数不支持传入自定义删除器，但<code>unique_ptr</code>&#x2F;<code>shared_ptr</code>的构造函数支持，此时只能直接<code>new</code>：</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">auto</span> widgetDeleter = [](Widget* pw) &#123; <span class="comment">/* 自定义析构逻辑 */</span> &#125;;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 必须用new：指定自定义删除器</span></span><br><span class="line"><span class="function">std::unique_ptr&lt;Widget, <span class="title">decltype</span><span class="params">(widgetDeleter)</span>&gt; <span class="title">upw</span><span class="params">(<span class="keyword">new</span> Widget, widgetDeleter)</span></span>;</span><br><span class="line"><span class="function">std::shared_ptr&lt;Widget&gt; <span class="title">spw</span><span class="params">(<span class="keyword">new</span> Widget, widgetDeleter)</span></span>;</span><br></pre></td></tr></table></figure><h4 id="需要用花括号初始化对象"><a href="#需要用花括号初始化对象" class="headerlink" title="需要用花括号初始化对象"></a>需要用花括号初始化对象</h4><p>make 函数的参数是通过<strong>小括号</strong>完美转发的，无法直接转发花括号初始化（<code>std::initializer_list</code>）。若要初始化<code>vector&lt;int&gt;{10,20}</code>这类对象，需先创建<code>initializer_list</code>再传递：</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 不推荐：make_unique用小括号，会创建10个值为20的元素，而非&#123;10,20&#125;</span></span><br><span class="line"><span class="keyword">auto</span> upv = std::make_unique&lt;std::vector&lt;<span class="type">int</span>&gt;&gt;(<span class="number">10</span>, <span class="number">20</span>);</span><br><span class="line"></span><br><span class="line"><span class="comment">// 正确：先创建initializer_list，再传递给make函数</span></span><br><span class="line"><span class="keyword">auto</span> initList = &#123;<span class="number">10</span>, <span class="number">20</span>&#125;;</span><br><span class="line"><span class="keyword">auto</span> spv = std::make_shared&lt;std::vector&lt;<span class="type">int</span>&gt;&gt;(initList);</span><br></pre></td></tr></table></figure><h4 id="针对shared-ptr的额外场景（unique-ptr无此限制）"><a href="#针对shared-ptr的额外场景（unique-ptr无此限制）" class="headerlink" title="针对shared_ptr的额外场景（unique_ptr无此限制）"></a>针对<code>shared_ptr</code>的额外场景（<code>unique_ptr</code>无此限制）</h4><ul><li>类重载了<code>operator new/operator delete</code>：这类自定义内存管理通常只适配 “对象大小” 的内存，而<code>make_shared</code>需要分配 “对象 + 控制块” 的内存，不兼容；</li><li>超大对象 +<code>weak_ptr</code>存活更久：<code>make_shared</code>的对象和控制块同内存，只有最后一个<code>shared_ptr</code>和最后一个<code>weak_ptr</code>都销毁，内存才释放；若超大对象的<code>shared_ptr</code>先销毁，<code>weak_ptr</code>还在，对象内存会一直占用（直接<code>new</code>则<code>shared_ptr</code>销毁后对象内存就释放，仅留控制块）。</li></ul><h2 id="无法用-make-函数时的异常安全写法"><a href="#无法用-make-函数时的异常安全写法" class="headerlink" title="无法用 make 函数时的异常安全写法"></a>无法用 make 函数时的异常安全写法</h2><p>如果必须用<code>new</code>（比如自定义删除器），要避免异常泄漏，需将 “<code>new</code>+ 智能指针构造” 单独放一行，再传递给函数；若想优化性能，可用<code>std::move</code>将左值转为右值（避免<code>shared_ptr</code>拷贝时的原子引用计数递增）：</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 第一步：单独语句创建shared_ptr，确保对象被接管</span></span><br><span class="line"><span class="function">std::shared_ptr&lt;Widget&gt; <span class="title">spw</span><span class="params">(<span class="keyword">new</span> Widget, cusDel)</span></span>;</span><br><span class="line"><span class="comment">// 第二步：传递时用std::move，高效且异常安全</span></span><br><span class="line"><span class="built_in">processWidget</span>(std::<span class="built_in">move</span>(spw), <span class="built_in">computePriority</span>());</span><br></pre></td></tr></table></figure><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><ol><li>优先用<code>make_unique/make_shared</code>的原因：消除代码重复、提升异常安全性，<code>make_shared</code>还能提升内存 &#x2F; 执行效率；</li><li>不能用 make 函数的场景：需自定义删除器、需花括号初始化，<code>shared_ptr</code>还需额外考虑自定义内存管理 &#x2F; 超大对象 +<code>weak_ptr</code>存活更久的情况；</li><li>无法用 make 函数时，需将<code>new</code>和智能指针构造单独放一行，保证异常安全，必要时用<code>std::move</code>优化性能。</li></ol>]]>
    </content>
    <id>https://devilsean.github.io/posts/81074862.html</id>
    <link href="https://devilsean.github.io/posts/81074862.html"/>
    <published>2026-03-15T12:00:00.000Z</published>
    <summary>对比 make 函数与直接 new 的异常安全和性能差异，并说明 make_shared 在自定义删除器和 weak_ptr 场景的限制。</summary>
    <title>Item21：优先使用make_unique和make_shared而非直接使用new</title>
    <updated>2026-10-30T11:04:54.088Z</updated>
  </entry>
  <entry>
    <author>
      <name>Sean</name>
    </author>
    <category term="Effective Modern C++" scheme="https://devilsean.github.io/categories/Effective-Modern-C/"/>
    <category term="4. 智能指针" scheme="https://devilsean.github.io/categories/Effective-Modern-C/4-%E6%99%BA%E8%83%BD%E6%8C%87%E9%92%88/"/>
    <category term="C++" scheme="https://devilsean.github.io/tags/C/"/>
    <category term="weak_ptr" scheme="https://devilsean.github.io/tags/weak-ptr/"/>
    <content>
      <![CDATA[<p><code>std::weak_ptr</code> 是 <strong><code>std::shared_ptr</code> 的辅助智能指针</strong>，不独立存在，特点如下：</p><ol><li>不参与资源所有权共享，<strong>不影响指向对象的引用计数</strong></li><li>能检测自身是否<strong>悬空（过期）</strong>，即指向的对象是否已被销毁</li><li>无法直接解引用、无法直接判空，需借助 <code>std::shared_ptr</code> 实现安全访问</li></ol><h2 id="基础用法"><a href="#基础用法" class="headerlink" title="基础用法"></a>基础用法</h2><h3 id="创建"><a href="#创建" class="headerlink" title="创建"></a>创建</h3><p>从 <code>std::shared_ptr</code> 直接创建，两者指向同一对象，且不会增加对象的引用计数：</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">auto</span> spw = std::<span class="built_in">make_shared</span>&lt;Widget&gt;(); <span class="comment">// 对象引用计数(RC)=1</span></span><br><span class="line"><span class="function">std::weak_ptr&lt;Widget&gt; <span class="title">wpw</span><span class="params">(spw)</span></span>;        <span class="comment">// RC仍为1，wpw与spw指向同一对象</span></span><br><span class="line">spw = <span class="literal">nullptr</span>;                         <span class="comment">// RC=0，对象销毁，wpw悬空（过期）</span></span><br></pre></td></tr></table></figure><h3 id="安全访问对象（解决竞态条件）"><a href="#安全访问对象（解决竞态条件）" class="headerlink" title="安全访问对象（解决竞态条件）"></a>安全访问对象（解决竞态条件）</h3><p>单独调用 <code>expired()</code> 判断是否过期存在竞态风险（检查后到访问前，对象可能被其他线程销毁），需通过<strong>原子操作</strong>同时完成 “检查 + 访问”，有两种方式：</p><table><thead><tr><th>方式</th><th>操作</th><th>效果</th></tr></thead><tbody><tr><td><code>lock()</code> 成员函数</td><td><code>auto sp = wpw.lock();</code></td><td>若 <code>wpw</code> 未过期，返回指向对象的 <code>shared_ptr</code>；若过期，返回空 <code>shared_ptr</code></td></tr><tr><td><code>shared_ptr</code> 构造函数</td><td><code>std::shared_ptr&lt;Widget&gt; sp(wpw);</code></td><td>若 <code>wpw</code> 未过期，构造成功；若过期，抛出 <code>std::bad_weak_ptr</code> 异常</td></tr></tbody></table><h2 id="应用场景"><a href="#应用场景" class="headerlink" title="应用场景"></a>应用场景</h2><p><code>std::weak_ptr</code> 用于处理 <strong><code>shared_ptr</code> 可能悬空</strong>的场景，有三类：</p><h3 id="1-可自动清理的缓存（工厂函数优化）"><a href="#1-可自动清理的缓存（工厂函数优化）" class="headerlink" title="1. 可自动清理的缓存（工厂函数优化）"></a>1. 可自动清理的缓存（工厂函数优化）</h3><ul><li><p>需求：工厂函数加载对象的操作昂贵，需缓存结果；同时要求对象无人使用时，缓存自动销毁，避免占用资源</p></li><li><p>实现方案：</p><ul><li>缓存容器存储 <code>std::weak_ptr&lt;const Widget&gt;</code>，不影响对象生命周期</li><li>工厂函数返回 <code>std::shared_ptr&lt;const Widget&gt;</code>，由调用者管理对象生命周期</li><li>每次请求时，通过 <code>lock()</code> 检查缓存对象是否有效，无效则重新加载并缓存</li></ul></li></ul><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="function">std::shared_ptr&lt;<span class="type">const</span> Widget&gt; <span class="title">fastLoadWidget</span><span class="params">(WidgetID id)</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">    <span class="type">static</span> std::unordered_map&lt;WidgetID, std::weak_ptr&lt;<span class="type">const</span> Widget&gt;&gt; cache;</span><br><span class="line">    <span class="keyword">auto</span> objPtr = cache[id].<span class="built_in">lock</span>(); <span class="comment">// 原子化检查+获取</span></span><br><span class="line">    <span class="keyword">if</span> (!objPtr) &#123;</span><br><span class="line">        objPtr = <span class="built_in">loadWidget</span>(id);    <span class="comment">// 加载新对象</span></span><br><span class="line">        cache[id] = objPtr;         <span class="comment">// 存入缓存</span></span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">return</span> objPtr;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="2-观察者模式的观察者列表"><a href="#2-观察者模式的观察者列表" class="headerlink" title="2. 观察者模式的观察者列表"></a>2. 观察者模式的观察者列表</h3><ul><li>需求：被观察者（subject）需持有观察者（observer）的指针，用于状态变更通知；但被观察者不控制观察者的生命周期，需避免访问已销毁的观察者</li><li>实现方案：被观察者的观察者列表存储 <code>std::weak_ptr&lt;Observer&gt;</code>，通知前通过 <code>lock()</code> 检查观察者是否有效，无效则跳过</li></ul><h3 id="3-打破-std-shared-ptr-循环引用"><a href="#3-打破-std-shared-ptr-循环引用" class="headerlink" title="3. 打破 std::shared_ptr 循环引用"></a>3. 打破 <code>std::shared_ptr</code> 循环引用</h3><ul><li>问题：若两个对象互相持有对方的 <code>std::shared_ptr</code>，会形成循环引用，导致引用计数无法归零，对象无法销毁，造成内存泄漏</li><li>解决：将其中一方的 <code>shared_ptr</code> 替换为 <code>weak_ptr</code>，<code>weak_ptr</code> 不影响对方的引用计数，打破循环</li><li>补充：严格分层结构（如树）中，子节点生命周期由父节点管理，子到父的反向引用可用<strong>原始指针</strong>，无需 <code>weak_ptr</code></li></ul><h2 id="效率特性"><a href="#效率特性" class="headerlink" title="效率特性"></a>效率特性</h2><ol><li>大小：与 <code>std::shared_ptr</code> 相同</li><li>底层：与 <code>std::shared_ptr</code> 共用<strong>控制块</strong></li><li>性能：构造、析构、赋值操作涉及<strong>原子操作</strong>，效率与 <code>shared_ptr</code> 基本一致</li><li>引用计数细节：控制块存在<strong>两个引用计数</strong>，<code>weak_ptr</code> 操作的是第二个计数</li></ol><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><ol><li>用 <code>std::weak_ptr</code> 替代可能悬空的 <code>std::shared_ptr</code>，避免悬空访问的未定义行为</li><li><code>std::weak_ptr</code> 的三大典型场景：<strong>缓存</strong>、<strong>观察者列表</strong>、<strong>打破 shared_ptr 循环引用</strong></li></ol>]]>
    </content>
    <id>https://devilsean.github.io/posts/72706006.html</id>
    <link href="https://devilsean.github.io/posts/72706006.html"/>
    <published>2026-03-15T12:00:00.000Z</published>
    <summary>说明 weak_ptr 如何观察 shared_ptr 管理的对象、检测悬空并打破循环引用，以及安全访问的写法。</summary>
    <title>Item20：当shared_ptr可能悬空时使用weak_ptr</title>
    <updated>2026-10-30T11:04:54.068Z</updated>
  </entry>
  <entry>
    <author>
      <name>Sean</name>
    </author>
    <category term="Effective Modern C++" scheme="https://devilsean.github.io/categories/Effective-Modern-C/"/>
    <category term="3. 转向现代C++" scheme="https://devilsean.github.io/categories/Effective-Modern-C/3-%E8%BD%AC%E5%90%91%E7%8E%B0%E4%BB%A3C/"/>
    <category term="C++" scheme="https://devilsean.github.io/tags/C/"/>
    <category term="delete" scheme="https://devilsean.github.io/tags/delete/"/>
    <content>
      <![CDATA[<p>主要考虑复制构造函数和复制赋值运算符：C++ 会自动生成这类特殊成员函数，但部分场景（如<code>iostream</code>的<code>basic_ios</code>类）中拷贝操作存在逻辑歧义（如流的读取状态、资源句柄拷贝混乱），因此需要主动禁止这些函数被调用。</p><h2 id="C-98-禁止函数调用的方案：私有声明-未定义"><a href="#C-98-禁止函数调用的方案：私有声明-未定义" class="headerlink" title="C++98 禁止函数调用的方案：私有声明 + 未定义"></a>C++98 禁止函数调用的方案：私有声明 + 未定义</h2><p>为阻止复制构造 &#x2F; 赋值运算符被使用，C++98 采取 “声明为 private + 不定义” 的方式：</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">template</span> &lt;<span class="keyword">class</span> <span class="title class_">charT</span>, <span class="keyword">class</span> <span class="title class_">traits</span> = char_traits&lt;charT&gt;&gt;</span><br><span class="line"><span class="keyword">class</span> basic_ios : <span class="keyword">public</span> ios_base &#123;</span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line">    …</span><br><span class="line"><span class="keyword">private</span>:</span><br><span class="line">    <span class="built_in">basic_ios</span>(<span class="type">const</span> basic_ios&amp; );           <span class="comment">// not defined</span></span><br><span class="line">    basic_ios&amp; <span class="keyword">operator</span>=(<span class="type">const</span> basic_ios&amp;); <span class="comment">// not defined</span></span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><ul><li>作用逻辑：<code>private</code>修饰阻止客户端代码调用；故意不定义函数，若成员 &#x2F; 友元函数误调用，会在<strong>链接期</strong>触发 “缺少函数定义” 错误。</li><li>缺陷：报错时机晚，仅链接期提示错误，排查成本高。</li></ul><h2 id="C-11-更优方案：deleted-函数（-delete）"><a href="#C-11-更优方案：deleted-函数（-delete）" class="headerlink" title="C++11 更优方案：deleted 函数（&#x3D; delete）"></a>C++11 更优方案：deleted 函数（&#x3D; delete）</h2><p>C++11 通过<code>= delete</code>标记函数为 deleted，实现禁止调用的目的：</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">template</span> &lt;<span class="keyword">class</span> <span class="title class_">charT</span>, <span class="keyword">class</span> <span class="title class_">traits</span> = char_traits&lt;charT&gt;&gt;</span><br><span class="line"><span class="keyword">class</span> basic_ios : <span class="keyword">public</span> ios_base &#123;</span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line"> ...</span><br><span class="line"> <span class="built_in">basic_ios</span>(<span class="type">const</span> basic_ios&amp;) = <span class="keyword">delete</span>;</span><br><span class="line"> basic_ios&amp; <span class="keyword">operator</span>=(<span class="type">const</span> basic_ios&amp;) = <span class="keyword">delete</span>;</span><br><span class="line"> ...</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><h3 id="deleted-函数的基础优势（对比-C-98）"><a href="#deleted-函数的基础优势（对比-C-98）" class="headerlink" title="deleted 函数的基础优势（对比 C++98）"></a>deleted 函数的基础优势（对比 C++98）</h3><ol><li>调用限制更严格：deleted 函数无法以任何形式被调用（包括成员 &#x2F; 友元函数调用），且<strong>编译期</strong>直接报错，报错时机更早、排查更易；</li><li>错误提示更精准：deleted 函数建议声明为<code>public</code>（C++ 先检查访问性，再检查 deleted 状态），编译器会直接提示 “函数已被删除”，而非仅提示 “private”。</li></ol><h3 id="deleted-函数的扩展优势（C-98-方案无法实现）"><a href="#deleted-函数的扩展优势（C-98-方案无法实现）" class="headerlink" title="deleted 函数的扩展优势（C++98 方案无法实现）"></a>deleted 函数的扩展优势（C++98 方案无法实现）</h3><p><code>private</code>仅能作用于类成员函数，而<strong>任何函数都可标记为 deleted</strong>，典型场景如下：</p><ol><li>禁止非成员函数的不合理隐式调用：</li></ol><p>过滤非目标类型的隐式转换调用（如仅允许 int 类型调用<code>isLucky</code>，禁止 char&#x2F;bool&#x2F;double）：</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="type">bool</span> <span class="title">isLucky</span><span class="params">(<span class="type">int</span> number)</span></span>;</span><br><span class="line"><span class="function"><span class="type">bool</span> <span class="title">isLucky</span><span class="params">(<span class="type">char</span>)</span> </span>= <span class="keyword">delete</span>;    <span class="comment">// 拒绝char类型</span></span><br><span class="line"><span class="function"><span class="type">bool</span> <span class="title">isLucky</span><span class="params">(<span class="type">bool</span>)</span> </span>= <span class="keyword">delete</span>;    <span class="comment">// 拒绝bool类型</span></span><br><span class="line"><span class="function"><span class="type">bool</span> <span class="title">isLucky</span><span class="params">(<span class="type">double</span>)</span> </span>= <span class="keyword">delete</span>;  <span class="comment">// 拒绝float/double类型</span></span><br></pre></td></tr></table></figure><ol><li>禁止模板的特定实例化：</li></ol><p>拒绝模板针对<code>void*</code>&#x2F;<code>char*</code>等特殊类型的实例化：</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">template</span>&lt;<span class="keyword">typename</span> T&gt;</span></span><br><span class="line"><span class="function"><span class="type">void</span> <span class="title">processPointer</span><span class="params">(T* ptr)</span></span>;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 禁止void*和char*实例化</span></span><br><span class="line"><span class="keyword">template</span>&lt;&gt;</span><br><span class="line"><span class="type">void</span> <span class="built_in">processPointer</span>&lt;<span class="type">void</span>&gt;(<span class="type">void</span>*) = <span class="keyword">delete</span>;</span><br><span class="line"><span class="keyword">template</span>&lt;&gt;</span><br><span class="line"><span class="type">void</span> <span class="built_in">processPointer</span>&lt;<span class="type">char</span>&gt;(<span class="type">char</span>*) = <span class="keyword">delete</span>;</span><br></pre></td></tr></table></figure><p>若需彻底禁止，还需删除<code>const void*</code>、<code>const volatile void*</code>、<code>const char*</code>、<code>const volatile char*</code>重载版本，以及<code>std::wchar_t</code>、<code>std::char16_t</code>、<code>std::char32_t</code>等字符类型的重载版本。</p><h2 id="补充：private-无法实现类内模板函数的特定实例化禁止"><a href="#补充：private-无法实现类内模板函数的特定实例化禁止" class="headerlink" title="补充：private 无法实现类内模板函数的特定实例化禁止"></a>补充：private 无法实现类内模板函数的特定实例化禁止</h2><p>模板特例化必须位于命名空间作用域，而非类作用域，因此在类内用<code>private</code>声明模板特化版本会直接编译错误；而 <code>deleted</code> 函数可在类外（命名空间作用域）删除类内模板的特化版本，无此限制：</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">Widget</span> &#123;</span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line">    <span class="function"><span class="keyword">template</span>&lt;<span class="keyword">typename</span> T&gt;</span></span><br><span class="line"><span class="function">    <span class="type">void</span> <span class="title">processPointer</span><span class="params">(T* ptr)</span> </span>&#123; <span class="comment">/* ... */</span> &#125;</span><br><span class="line">&#125;;</span><br><span class="line"><span class="comment">// 类外删除void*版本的特化，编译通过且实现禁止调用</span></span><br><span class="line"><span class="keyword">template</span>&lt;&gt;</span><br><span class="line"><span class="type">void</span> Widget::<span class="built_in">processPointer</span>&lt;<span class="type">void</span>&gt;(<span class="type">void</span>*) = <span class="keyword">delete</span>;</span><br></pre></td></tr></table></figure><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><ul><li>优先选用删除函数，而非<code>private</code>函数</li><li>任何函数都可以删除，包括非成员函数和模板具现。</li></ul>]]>
    </content>
    <id>https://devilsean.github.io/posts/ab91616b.html</id>
    <link href="https://devilsean.github.io/posts/ab91616b.html"/>
    <published>2026-03-15T12:00:00.000Z</published>
    <summary>对比 C++98 的 private 未定义函数与 C++11 的 =delete，说明删除函数在重载解析和错误提示上的优势。</summary>
    <title>Item11：优先选用删除函数，而非private未定义函数</title>
    <updated>2026-10-30T11:04:53.874Z</updated>
  </entry>
  <entry>
    <author>
      <name>Sean</name>
    </author>
    <category term="Effective Modern C++" scheme="https://devilsean.github.io/categories/Effective-Modern-C/"/>
    <category term="3. 转向现代C++" scheme="https://devilsean.github.io/categories/Effective-Modern-C/3-%E8%BD%AC%E5%90%91%E7%8E%B0%E4%BB%A3C/"/>
    <category term="C++" scheme="https://devilsean.github.io/tags/C/"/>
    <category term="override" scheme="https://devilsean.github.io/tags/override/"/>
    <content>
      <![CDATA[<p>面向对象编程的基本理念是：在派生类中虚函数实现，会改写基类中对应虚函数的实现。</p><h2 id="虚函数重写的约束条件"><a href="#虚函数重写的约束条件" class="headerlink" title="虚函数重写的约束条件"></a>虚函数重写的约束条件</h2><p>要想重写一个函数，必须满足下列要求：</p><ul><li>基类中的函数必须是虚函数。</li><li>基类和派生类中的函数名字必须完全相同（析构函数例外）。</li><li>基类和派生类中的函数形参类型必须完全相同。</li><li>基类和派生类中的函数常量性必须完全相同。</li><li>基类和派生类中的函数返回值和异常规格必须兼容。</li><li>基类和派生类中的函数引用限定符必须完全相同。（C++11新增）</li></ul><blockquote><p>引用限定符：限定类的非静态成员函数只能被左值对象或右值对象调用。<br>带<code>&amp;</code>的成员函数仅允许左值对象调用；<br>带<code>&amp;&amp;</code>的成员函数仅允许右值对象调用。</p></blockquote><p>如果基类中的虚函数带有引用饰词，则派生类要对该函数进行改写版本必须也带有完全相同的引用饰词。否则相当于重新定义了一个新的函数。</p><p>编译器可能不报警但是存在错误的例子：</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">Base</span> &#123;</span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line">    <span class="function"><span class="keyword">virtual</span> <span class="type">void</span> <span class="title">mf1</span><span class="params">()</span> <span class="type">const</span></span>;</span><br><span class="line">    <span class="function"><span class="keyword">virtual</span> <span class="type">void</span> <span class="title">mf2</span><span class="params">(<span class="type">int</span> x)</span></span>;</span><br><span class="line">    <span class="function"><span class="keyword">virtual</span> <span class="type">void</span> <span class="title">mf3</span><span class="params">()</span> &amp;</span>;</span><br><span class="line">    <span class="function"><span class="type">void</span> <span class="title">mf4</span><span class="params">()</span> <span class="type">const</span></span>;</span><br><span class="line">&#125;;</span><br><span class="line"></span><br><span class="line"><span class="keyword">class</span> <span class="title class_">Derived</span>: <span class="keyword">public</span> Base &#123;</span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line">    <span class="function"><span class="keyword">virtual</span> <span class="type">void</span> <span class="title">mf1</span><span class="params">()</span></span>; <span class="comment">// mf1是声明为const的，而在派生类中没有</span></span><br><span class="line">    <span class="function"><span class="keyword">virtual</span> <span class="type">void</span> <span class="title">mf2</span><span class="params">(<span class="type">unsigned</span> <span class="type">int</span> x)</span></span>; <span class="comment">// mf2的形参类型是int，而在派生类中的则是unsigned int</span></span><br><span class="line">    <span class="function"><span class="keyword">virtual</span> <span class="type">void</span> <span class="title">mf3</span><span class="params">()</span> &amp;&amp;</span>; <span class="comment">// mf3带有左值引用限定符，而派生类中的则是右值引用限定符</span></span><br><span class="line">    <span class="function"><span class="type">void</span> <span class="title">mf4</span><span class="params">()</span> <span class="type">const</span></span>; <span class="comment">// mf4未声明为虚函数</span></span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><h2 id="override-声明：显式保证重写正确性"><a href="#override-声明：显式保证重写正确性" class="headerlink" title="override 声明：显式保证重写正确性"></a>override 声明：显式保证重写正确性</h2><p>通过在派生类函数后添加<code>override</code>声明，显式标明该函数意图改写基类虚函数版本：</p><ul><li>编译器会强制校验所有重写条件，不满足则直接编译报错（而非运行时才暴露问题）；</li><li>额外价值：修改基类虚函数签名时，可通过 “编译报错的派生类数量” 衡量影响面，判断修改代价是否值得。<br>例：</li></ul><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">Derived</span>: <span class="keyword">public</span> Base &#123;</span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line">    <span class="function"><span class="keyword">virtual</span> <span class="type">void</span> <span class="title">mf1</span><span class="params">()</span> <span class="type">const</span> <span class="keyword">override</span></span>;  <span class="comment">// 匹配const限定</span></span><br><span class="line">    <span class="function"><span class="keyword">virtual</span> <span class="type">void</span> <span class="title">mf2</span><span class="params">(<span class="type">int</span> x)</span> <span class="keyword">override</span></span>;   <span class="comment">// 匹配int形参</span></span><br><span class="line">    <span class="function"><span class="keyword">virtual</span> <span class="type">void</span> <span class="title">mf3</span><span class="params">()</span> &amp; <span class="keyword">override</span></span>;      <span class="comment">// 匹配左值引用限定</span></span><br><span class="line">    <span class="function"><span class="type">void</span> <span class="title">mf4</span><span class="params">()</span> <span class="type">const</span> <span class="keyword">override</span></span>;          <span class="comment">// 基类需补充virtual，否则仍报错</span></span><br><span class="line">&#125;; </span><br></pre></td></tr></table></figure><h2 id="新增两个语境关键字：override和final"><a href="#新增两个语境关键字：override和final" class="headerlink" title="新增两个语境关键字：override和final"></a>新增两个语境关键字：override和final</h2><p>语言保留这两个关键字，但是仅在特定语境下保留。只在特定语境下用作关键字，其他情况下可用作变量名。</p><p>例：</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">Warning</span> &#123;         <span class="comment">//C++98潜在的传统类代码</span></span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line">    …</span><br><span class="line">    <span class="function"><span class="type">void</span> <span class="title">override</span><span class="params">()</span></span>;    <span class="comment">//C++98和C++11都合法（且含义相同）</span></span><br><span class="line">    …</span><br><span class="line">&#125;;</span><br><span class="line"></span><br><span class="line"><span class="keyword">class</span> <span class="title class_">Base</span> &#123; </span><br><span class="line"><span class="keyword">public</span>: </span><br><span class="line"> <span class="function"><span class="keyword">virtual</span> <span class="type">void</span> <span class="title">func</span><span class="params">()</span> <span class="keyword">final</span></span>; <span class="comment">// 禁止派生类重写func </span></span><br><span class="line">&#125;; </span><br><span class="line"><span class="keyword">class</span> <span class="title class_">FinalClass</span> <span class="keyword">final</span> &#123;&#125;; <span class="comment">// 禁止FinalClass被继承 </span></span><br><span class="line"><span class="comment">// class SubClass : public FinalClass &#123;&#125;; // 编译错误</span></span><br></pre></td></tr></table></figure><h2 id="关于成员函数引用限定符"><a href="#关于成员函数引用限定符" class="headerlink" title="关于成员函数引用限定符"></a>关于成员函数引用限定符</h2><p>编写一个函数仅接受传入左值实参，需要声明一个非<code>const</code>左值引用形参：</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="type">void</span> <span class="title">doSomething</span><span class="params">(Widget&amp; w)</span></span>; <span class="comment">// 仅接受左值的Widgets类型</span></span><br></pre></td></tr></table></figure><p>编写一个函数仅接受传入右值实参，需要声明一个右值引用形参：</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="type">void</span> <span class="title">doSomething</span><span class="params">(Widegt&amp; w)</span></span>; <span class="comment">// 仅接受右值的Widgets类型</span></span><br></pre></td></tr></table></figure><p>成员函数引用限定符的作用就是针对发起成员函数调用的对象，即<code>*this</code>进行区分（是左值对象还是右值对象）；<code>const</code>限定符的作用就是约束<code>*this</code>的常量性（是否是<code>const</code>对象）</p><p>左值引用类型的重载版本返回的是一个左值引用（一个左值），右值引用类型的重载版本返回的是一个临时对象（一个右值）。</p><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><ul><li>为需要重写的函数添加<code>override</code>声明。</li><li>成员函数引用限定符使得对于左值和右值对象（<code>*this</code>）的处理能够区分开来。</li></ul>]]>
    </content>
    <id>https://devilsean.github.io/posts/359d6da1.html</id>
    <link href="https://devilsean.github.io/posts/359d6da1.html"/>
    <published>2026-03-15T12:00:00.000Z</published>
    <summary>梳理虚函数重写的完整约束，说明 override 如何让编译器检查签名，避免因参数、const 或引用限定符不一致而意外隐藏基类函数。</summary>
    <title>Item12：使用override重写声明函数</title>
    <updated>2026-10-30T11:04:53.888Z</updated>
  </entry>
  <entry>
    <author>
      <name>Sean</name>
    </author>
    <category term="Effective Modern C++" scheme="https://devilsean.github.io/categories/Effective-Modern-C/"/>
    <category term="4. 智能指针" scheme="https://devilsean.github.io/categories/Effective-Modern-C/4-%E6%99%BA%E8%83%BD%E6%8C%87%E9%92%88/"/>
    <category term="C++" scheme="https://devilsean.github.io/tags/C/"/>
    <category term="Pimpl" scheme="https://devilsean.github.io/tags/Pimpl/"/>
    <content>
      <![CDATA[<h2 id="Pimpl-的价值"><a href="#Pimpl-的价值" class="headerlink" title="Pimpl 的价值"></a>Pimpl 的价值</h2><p>Pimpl 的目的是<strong>减少编译依赖、加快编译速度</strong>：</p><ul><li>传统写法中，<code>Widget</code>的头文件依赖<code>std::string</code>、<code>std::vector</code>、<code>Gadget</code>等头文件，一旦这些依赖变更，所有包含<code>Widget.h</code>的代码都要重新编译；</li><li>Pimpl 把这些依赖移到<code>.cpp</code>文件中，头文件仅前向声明<code>Impl</code>结构体，用指针指向它，彻底切断 “头文件 - 实现依赖” 的关联。</li></ul><p>C++98 用原始指针实现 Pimpl，现代 C++ 优先用<code>std::unique_ptr</code>（因为 pImpl 是 “独占所有权”），但直接用<code>unique_ptr</code>会踩坑。</p><h2 id="用std-unique-ptr做-Pimpl-会编译报错"><a href="#用std-unique-ptr做-Pimpl-会编译报错" class="headerlink" title="用std::unique_ptr做 Pimpl 会编译报错"></a>用<code>std::unique_ptr</code>做 Pimpl 会编译报错</h2><h4 id="1-报错现象"><a href="#1-报错现象" class="headerlink" title="1. 报错现象"></a>1. 报错现象</h4><p>头文件仅前向声明<code>Impl</code>，用<code>std::unique_ptr&lt;Impl&gt; pImpl</code>，且未手动声明析构函数时，用户创建<code>Widget</code>对象会编译报错（提示 “sizeof&#x2F;delete 应用到不完整类型”）：</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// widget.h（有问题的写法）</span></span><br><span class="line"><span class="keyword">class</span> <span class="title class_">Widget</span> &#123;</span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line">    <span class="built_in">Widget</span>() : <span class="built_in">pImpl</span>(std::<span class="built_in">make_unique</span>&lt;Impl&gt;()) &#123;&#125; <span class="comment">// 仅前向声明Impl</span></span><br><span class="line"><span class="keyword">private</span>:</span><br><span class="line">    <span class="keyword">struct</span> <span class="title class_">Impl</span>;</span><br><span class="line">    std::unique_ptr&lt;Impl&gt; pImpl;</span><br><span class="line">&#125;;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 客户代码（编译报错）</span></span><br><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string">&quot;widget.h&quot;</span></span></span><br><span class="line">Widget w; <span class="comment">// 错误：Impl是不完整类型，unique_ptr析构时检查失败</span></span><br></pre></td></tr></table></figure><h4 id="2-报错根源"><a href="#2-报错根源" class="headerlink" title="2. 报错根源"></a>2. 报错根源</h4><ul><li>编译器会<strong>自动生成</strong><code>Widget</code>的析构函数（因为你没手动声明），且析构函数是<code>inline</code>的（在头文件里处理）；</li><li><code>Widget</code>析构时要调用<code>unique_ptr&lt;Impl&gt;</code>的析构函数，而<code>unique_ptr</code>的默认删除器会用<code>static_assert</code>检查：指向的类型必须是<strong>完整类型</strong>（有完整定义）；</li><li>但<code>Impl</code>在头文件里只是前向声明（不完整类型），因此触发编译错误。</li></ul><h2 id="特殊成员函数-“声明在-h，定义在-cpp”"><a href="#特殊成员函数-“声明在-h，定义在-cpp”" class="headerlink" title="特殊成员函数 “声明在.h，定义在.cpp”"></a>特殊成员函数 “声明在.h，定义在.cpp”</h2><p>要让<code>unique_ptr</code>正常工作，必须保证：在处理<code>unique_ptr</code>析构 &#x2F; 移动逻辑时，<code>Impl</code>是完整类型（已在<code>.cpp</code> 里定义）。因此需要对 3 类特殊成员函数做统一处理：</p><h4 id="1-析构函数"><a href="#1-析构函数" class="headerlink" title="1. 析构函数"></a>1. 析构函数</h4><ul><li>头文件（.h）：仅声明析构函数，不定义；</li><li>实现文件（.cpp）：在<code>Impl</code>完整定义后，定义析构函数（哪怕用<code>= default</code>）。</li></ul><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// widget.h</span></span><br><span class="line"><span class="keyword">class</span> <span class="title class_">Widget</span> &#123;</span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line">    <span class="built_in">Widget</span>();</span><br><span class="line">    ~<span class="built_in">Widget</span>(); <span class="comment">// 仅声明</span></span><br><span class="line"><span class="keyword">private</span>:</span><br><span class="line">    <span class="keyword">struct</span> <span class="title class_">Impl</span>;</span><br><span class="line">    std::unique_ptr&lt;Impl&gt; pImpl;</span><br><span class="line">&#125;;</span><br><span class="line"></span><br><span class="line"><span class="comment">// widget.cpp</span></span><br><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string">&quot;widget.h&quot;</span></span></span><br><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string">&lt;string&gt;</span></span></span><br><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string">&lt;vector&gt;</span></span></span><br><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string">&quot;gadget.h&quot;</span></span></span><br><span class="line"></span><br><span class="line"><span class="comment">// 先定义Impl（此时Impl是完整类型）</span></span><br><span class="line"><span class="keyword">struct</span> <span class="title class_">Widget</span>::Impl &#123;</span><br><span class="line">    std::string name;</span><br><span class="line">    std::vector&lt;<span class="type">double</span>&gt; data;</span><br><span class="line">    Gadget g1, g2, g3;</span><br><span class="line">&#125;;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 再定义析构函数（= default等价于编译器自动生成的逻辑）</span></span><br><span class="line">Widget::~<span class="built_in">Widget</span>() = <span class="keyword">default</span>;</span><br><span class="line">Widget::<span class="built_in">Widget</span>() : <span class="built_in">pImpl</span>(std::<span class="built_in">make_unique</span>&lt;Impl&gt;()) &#123;&#125;</span><br></pre></td></tr></table></figure><h4 id="2-移动操作（移动构造-移动赋值）"><a href="#2-移动操作（移动构造-移动赋值）" class="headerlink" title="2. 移动操作（移动构造 &#x2F; 移动赋值）"></a>2. 移动操作（移动构造 &#x2F; 移动赋值）</h4><ul><li>问题：声明析构函数后，编译器不会自动生成移动操作；若在.h 里用<code>= default</code>定义移动操作，会触发和析构一样的 “不完整类型” 报错；</li><li>解决：<code>.h</code> 里仅声明移动操作，<code>.cpp</code> 里在<code>Impl</code>定义后用<code>= default</code>定义（编译器生成的移动逻辑对<code>unique_ptr</code>是正确的 —— 移动指针所有权）。</li></ul><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// widget.h</span></span><br><span class="line"><span class="keyword">class</span> <span class="title class_">Widget</span> &#123;</span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line">    <span class="built_in">Widget</span>();</span><br><span class="line">    ~<span class="built_in">Widget</span>();</span><br><span class="line">    <span class="built_in">Widget</span>(Widget&amp;&amp; rhs); <span class="comment">// 仅声明</span></span><br><span class="line">    Widget&amp; <span class="keyword">operator</span>=(Widget&amp;&amp; rhs); <span class="comment">// 仅声明</span></span><br><span class="line"><span class="keyword">private</span>:</span><br><span class="line">    <span class="keyword">struct</span> <span class="title class_">Impl</span>;</span><br><span class="line">    std::unique_ptr&lt;Impl&gt; pImpl;</span><br><span class="line">&#125;;</span><br><span class="line"></span><br><span class="line"><span class="comment">// widget.cpp</span></span><br><span class="line">Widget::<span class="built_in">Widget</span>(Widget&amp;&amp; rhs) = <span class="keyword">default</span>; <span class="comment">// 定义在Impl之后</span></span><br><span class="line">Widget&amp; <span class="keyword">operator</span>=(Widget&amp;&amp; rhs) = <span class="keyword">default</span>;</span><br></pre></td></tr></table></figure><h4 id="3-拷贝操作（拷贝构造-拷贝赋值）"><a href="#3-拷贝操作（拷贝构造-拷贝赋值）" class="headerlink" title="3. 拷贝操作（拷贝构造 &#x2F; 拷贝赋值）"></a>3. 拷贝操作（拷贝构造 &#x2F; 拷贝赋值）</h4><ul><li><p>问题：</p><ol><li>包含<code>unique_ptr</code>的类，编译器不会自动生成拷贝操作（<code>unique_ptr</code>是 “只可移动” 类型）；</li><li>即使生成，也是<strong>浅拷贝</strong>（仅复制指针，而非指针指向的<code>Impl</code>对象），不符合需求；</li></ol></li><li><p>解决：<code>.h</code> 里声明拷贝操作，<code>.cpp</code> 里实现<strong>深拷贝</strong>（复制<code>Impl</code>对象而非指针）：</p></li></ul><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// widget.h</span></span><br><span class="line"><span class="keyword">class</span> <span class="title class_">Widget</span> &#123;</span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line">    <span class="built_in">Widget</span>(<span class="type">const</span> Widget&amp; rhs); <span class="comment">// 仅声明</span></span><br><span class="line">    Widget&amp; <span class="keyword">operator</span>=(<span class="type">const</span> Widget&amp; rhs); <span class="comment">// 仅声明</span></span><br><span class="line">    <span class="comment">// 其他声明...</span></span><br><span class="line">&#125;;</span><br><span class="line"></span><br><span class="line"><span class="comment">// widget.cpp</span></span><br><span class="line"><span class="comment">// 拷贝构造：创建新的Impl对象，复制源对象的Impl内容</span></span><br><span class="line">Widget::<span class="built_in">Widget</span>(<span class="type">const</span> Widget&amp; rhs) : <span class="built_in">pImpl</span>(std::<span class="built_in">make_unique</span>&lt;Impl&gt;(*rhs.pImpl)) &#123;&#125;</span><br><span class="line"><span class="comment">// 拷贝赋值：直接复制Impl对象的内容（利用Impl自动生成的拷贝逻辑）</span></span><br><span class="line">Widget&amp; <span class="keyword">operator</span>=(<span class="type">const</span> Widget&amp; rhs) &#123;</span><br><span class="line">    *pImpl = *rhs.pImpl;</span><br><span class="line">    <span class="keyword">return</span> *<span class="keyword">this</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="std-unique-ptr-和-std-shared-ptr的差异"><a href="#std-unique-ptr-和-std-shared-ptr的差异" class="headerlink" title="std::unique_ptr 和 std::shared_ptr的差异"></a><code>std::unique_ptr</code> 和 <code>std::shared_ptr</code>的差异</h2><p>上述 “声明在.h、定义在.cpp” 的要求<strong>仅针对<code>unique_ptr</code></strong>，用<code>std::shared_ptr</code>时完全不需要：</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// widget.h（用shared_ptr的写法，无任何报错）</span></span><br><span class="line"><span class="keyword">class</span> <span class="title class_">Widget</span> &#123;</span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line">    <span class="built_in">Widget</span>() : <span class="built_in">pImpl</span>(std::<span class="built_in">make_shared</span>&lt;Impl&gt;()) &#123;&#125;</span><br><span class="line">    <span class="comment">// 无需声明析构/移动操作，编译器自动生成即可</span></span><br><span class="line"><span class="keyword">private</span>:</span><br><span class="line">    <span class="keyword">struct</span> <span class="title class_">Impl</span>;</span><br><span class="line">    std::shared_ptr&lt;Impl&gt; pImpl;</span><br><span class="line">&#125;;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 客户代码（正常编译）</span></span><br><span class="line">Widget w1;</span><br><span class="line"><span class="keyword">auto</span> w2 = std::<span class="built_in">move</span>(w1); <span class="comment">// 移动构造正常</span></span><br></pre></td></tr></table></figure><h4 id="底层原因"><a href="#底层原因" class="headerlink" title="底层原因"></a>底层原因</h4><ul><li><code>unique_ptr</code>：删除器的类型是智能指针的一部分，为了效率，析构 &#x2F; 移动时必须知道指向类型的完整定义；</li><li><code>shared_ptr</code>：删除器的类型不是智能指针的一部分，析构 &#x2F; 移动时不需要指向类型完整，因此无需额外处理。</li></ul><h4 id="选型建议"><a href="#选型建议" class="headerlink" title="选型建议"></a>选型建议</h4><ul><li>Pimpl 的核心是 “独占所有权”，因此<code>unique_ptr</code>是首选（符合语义），哪怕需要额外处理特殊成员函数；</li><li><code>shared_ptr</code>仅在需要 “共享所有权” 时使用，优势是无需处理特殊成员函数，但有引用计数的性能开销。</li></ul><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><ol><li>Pimpl 的价值是减少编译依赖、加快编译速度，现代 C++ 优先用<code>std::unique_ptr</code>实现 Pimpl；</li><li>对<code>std::unique_ptr</code>类型的 pImpl 指针，必须将析构、移动操作<strong>声明在头文件，定义在实现文件</strong>（拷贝操作需手动实现深拷贝），哪怕编译器自动生成的逻辑本身是正确的；</li><li>上述要求仅适用于<code>std::unique_ptr</code>，<code>std::shared_ptr</code>无需额外处理特殊成员函数，但不符合 Pimpl 的 “独占所有权” 语义，仅在共享场景使用。</li></ol>]]>
    </content>
    <id>https://devilsean.github.io/posts/38061a7c.html</id>
    <link href="https://devilsean.github.io/posts/38061a7c.html"/>
    <published>2026-03-15T12:00:00.000Z</published>
    <summary>介绍 Pimpl 降低编译依赖的思路，重点说明为何要把特殊成员函数定义放到实现文件中。</summary>
    <title>Item22：使用Pimpl习惯用法时，将特殊成员函数的定义放到实现文件中</title>
    <updated>2026-10-30T11:04:54.106Z</updated>
  </entry>
  <entry>
    <author>
      <name>Sean</name>
    </author>
    <category term="Effective Modern C++" scheme="https://devilsean.github.io/categories/Effective-Modern-C/"/>
    <category term="3. 转向现代C++" scheme="https://devilsean.github.io/categories/Effective-Modern-C/3-%E8%BD%AC%E5%90%91%E7%8E%B0%E4%BB%A3C/"/>
    <category term="C++" scheme="https://devilsean.github.io/tags/C/"/>
    <category term="const_iterator" scheme="https://devilsean.github.io/tags/const-iterator/"/>
    <content>
      <![CDATA[<p>STL 的<code>const_iterator</code>等价于 “指向常量的指针（pointer-to-const）”，指向的值不可修改，符合 “能加<code>const</code>就加” 的通用编程准则 —— 只要无需修改迭代器指向的值，就应优先使用<code>const_iterator</code>；但该迭代器的实用性随 C++ 版本迭代大幅提升，C++11 是关键转折点，同时通用代码中需优先使用非成员函数版本的<code>begin/end/cbegin</code>等。</p><h2 id="各-C-版本对-const-iterator-的支持差异"><a href="#各-C-版本对-const-iterator-的支持差异" class="headerlink" title="各 C++ 版本对 const_iterator 的支持差异"></a>各 C++ 版本对 const_iterator 的支持差异</h2><h3 id="1-C-98：支持残缺，实用性差"><a href="#1-C-98：支持残缺，实用性差" class="headerlink" title="1. C++98：支持残缺，实用性差"></a>1. C++98：支持残缺，实用性差</h3><p><code>const_iterator</code>理论上符合规范，但实际使用存在两大痛点：</p><ul><li><p><strong>创建麻烦</strong>：非<code>const</code>容器无法直接获取<code>const_iterator</code>，需强制类型转换（或绑定到<code>const</code>引用）才能得到；</p></li><li><p><strong>使用受限</strong>：<code>insert/erase</code>等容器操作仅接受<code>iterator</code>，且<code>const_iterator</code>无法可移植转换为<code>iterator</code>（即使<code>reinterpret_cast</code>也不行）；</p><p>  最终导致开发者被迫放弃使用，违背 “能<code>const</code>则<code>const</code>” 的准则。</p></li></ul><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">typedef</span> std::vector&lt;<span class="type">int</span>&gt;::iterator IterT; </span><br><span class="line"><span class="keyword">typedef</span> std::vector&lt;<span class="type">int</span>&gt;::const_iterator ConstIterT; </span><br><span class="line"></span><br><span class="line">std::vector&lt;<span class="type">int</span>&gt; values; </span><br><span class="line">… </span><br><span class="line"><span class="comment">// 非const容器需强制转换才能获取const_iterator </span></span><br><span class="line">ConstIterT ci = </span><br><span class="line"> std::<span class="built_in">find</span>(<span class="built_in">static_cast</span>&lt;ConstIterT&gt;(values.<span class="built_in">begin</span>()), </span><br><span class="line">     <span class="built_in">static_cast</span>&lt;ConstIterT&gt;(values.<span class="built_in">end</span>()), </span><br><span class="line">     <span class="number">1983</span>); </span><br><span class="line"><span class="comment">// insert不接受const_iterator，转换iterator也无可靠方法（编译失败） </span></span><br><span class="line">values.<span class="built_in">insert</span>(<span class="built_in">static_cast</span>&lt;IterT&gt;(ci), <span class="number">1998</span>);</span><br></pre></td></tr></table></figure><h3 id="C-11：支持完善，易用性大幅提升"><a href="#C-11：支持完善，易用性大幅提升" class="headerlink" title="C++11：支持完善，易用性大幅提升"></a>C++11：支持完善，易用性大幅提升</h3><p>解决了 C++98 的核心痛点，让<code>const_iterator</code>真正实用：</p><ul><li><p>容器新增<code>cbegin/cend</code>成员函数，非<code>const</code>容器也能直接获取<code>const_iterator</code>；</p></li><li><p><code>insert/erase</code>等操作支持接收<code>const_iterator</code>，无需转换即可使用；</p><p>  示例：无需修改迭代器指向值的查找 + 插入场景，可直接用<code>cbegin/cend</code>获取<code>const_iterator</code>，代码简洁且符合规范。</p></li></ul><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">std::vector&lt;<span class="type">int</span>&gt; values; </span><br><span class="line">… </span><br><span class="line"><span class="comment">// 直接用cbegin/cend获取const_iterator，insert原生支持 </span></span><br><span class="line"><span class="keyword">auto</span> it = std::<span class="built_in">find</span>(values.<span class="built_in">cbegin</span>(), values.<span class="built_in">cend</span>(), <span class="number">1983</span>); </span><br><span class="line">values.<span class="built_in">insert</span>(it, <span class="number">1998</span>);</span><br></pre></td></tr></table></figure><h3 id="C-11-的小缺陷与-C-14-的补全"><a href="#C-11-的小缺陷与-C-14-的补全" class="headerlink" title="C++11 的小缺陷与 C++14 的补全"></a>C++11 的小缺陷与 C++14 的补全</h3><ul><li>C++11 仅新增非成员函数<code>begin/end</code>，缺失<code>cbegin/cend/rbegin/rend</code>等；</li><li>C++14 补全了这些非成员函数，满足 “最大程度通用的库代码” 需求（适配原生数组、仅提供自由函数接口的第三方库等）。</li></ul><h2 id="通用代码的适配方案（C-11-兼容）"><a href="#通用代码的适配方案（C-11-兼容）" class="headerlink" title="通用代码的适配方案（C++11 兼容）"></a>通用代码的适配方案（C++11 兼容）</h2><p>若 C++11 环境下 STL 未提供非成员<code>cbegin</code>等函数，可自行实现（兼容普通容器和原生数组）：</p><ul><li>核心逻辑：通过<code>const</code>引用接收容器 &#x2F; 数组，调用 C++11 提供的非成员<code>begin</code>—— 对<code>const</code>容器 &#x2F; 数组，<code>begin</code>会自动返回<code>const_iterator</code>（数组场景下等价于指向<code>const</code>的指针）；</li><li>无需依赖容器的<code>cbegin</code>成员函数，适配性更强。</li></ul><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><ul><li>优先选用<code>const_iterator</code>，而非<code>iterator</code>。</li><li>在最通用的代码中，优先选用非成员函数版本的<code>begin</code>、<code>end</code>和<code>rbegin</code>等，而非其成员函数版本。</li></ul>]]>
    </content>
    <id>https://devilsean.github.io/posts/e8f61677.html</id>
    <link href="https://devilsean.github.io/posts/e8f61677.html"/>
    <published>2026-03-15T12:00:00.000Z</published>
    <summary>说明 const_iterator 的只读语义，以及 C++98 到 C++11 在通用代码中使用 begin/end/cbegin 的演进。</summary>
    <title>Item13：优先选用const_iterator，而非iterator</title>
    <updated>2026-10-30T11:04:53.905Z</updated>
  </entry>
  <entry>
    <author>
      <name>Sean</name>
    </author>
    <category term="Effective Modern C++" scheme="https://devilsean.github.io/categories/Effective-Modern-C/"/>
    <category term="3. 转向现代C++" scheme="https://devilsean.github.io/categories/Effective-Modern-C/3-%E8%BD%AC%E5%90%91%E7%8E%B0%E4%BB%A3C/"/>
    <category term="C++" scheme="https://devilsean.github.io/tags/C/"/>
    <category term="constexpr" scheme="https://devilsean.github.io/tags/constexpr/"/>
    <content>
      <![CDATA[<p><code>constexpr</code>作用于对象时，是加强版的<code>const</code>；但作用于函数时含义截然不同 —— 它并非简单限定 “返回 const 值” 或 “结果编译期可知”，而是根据调用场景动态适配。</p><ul><li><p><code>constexpr</code>对象：<strong>必然具备<code>const</code>只读属性，且必须由编译期可知的值完成初始化</strong>（技术上是翻译期，包含编译 + 链接，编程层面可视为编译期）。</p></li><li><p><code>constexpr</code>函数：<strong>不保证返回值是 const，也不保证结果一定编译期可知</strong>—— 结果是否编译期确定，完全取决于传入的实参：</p><ul><li>若实参是编译期已知的常量 → 结果在编译期计算，可用于 “整型常量表达式” 语境；</li><li>若实参是运行期才能确定的值 → 函数退化为普通函数，在运行期计算结果。</li></ul></li></ul><p>编译期可知的常量整型值，可用于 C++ 要求 “整型常量表达式” 的核心语境：数组尺寸、整型模板实参（如<code>std::array</code>的长度）、枚举量的值、对齐修饰符（<code>alignas</code>）等。普通<code>const</code>对象无法满足这些场景（仅保证只读，不保证编译期初始化），因此：</p><blockquote><p>所有<code>constexpr</code>对象都是<code>const</code>对象，但并非所有<code>const</code>对象都是<code>constexpr</code>对象。</p></blockquote><p><code>constexpr</code>函数的特性与限制：</p><ol><li><p>适用语境：可同时适配 “编译期常量计算” 和 “运行期普通计算”，无需为两种场景编写不同函数；</p></li><li><p>类型限制：仅能接收和返回<strong>字面值类型</strong>（即编译期可确定值的类型，如内置类型、符合条件的自定义类型）；</p></li><li><p>实现限制（C++ 版本差异）：</p><ul><li>C++11：函数体仅允许 1 行 return 语句，需用三元运算符替代 if-else、递归替代循环；</li><li>C++14：限制大幅放宽，支持循环、局部变量、void 返回值，甚至允许修改对象状态的成员函数（如自定义类型的 setter）。</li></ul></li></ol><p>自定义类型对<code>constexpr</code>的支持：</p><ul><li>C++11：仅构造函数、返回字面值类型的 const getter 可声明为<code>constexpr</code>（成员函数隐式 const）；</li><li>C++14：可将修改对象状态的 setter 声明为<code>constexpr</code>（void 返回值也允许），能基于编译期对象完成更复杂的编译期计算（如 Point 类的中点、镜像点计算）。</li></ul><p>注意：<code>constexpr</code>是对象 &#x2F; 函数<strong>接口的核心组成部分</strong>—— 声明后若随意移除（如函数新增 I&#x2F;O 语句），依赖它的客户端代码（如用在模板参数、数组大小）会直接编译失败，需长期遵守。</p><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><ul><li><code>constexpr</code>对象都具备<code>const</code>属性，并由编译期已知的值完成初始化。</li><li><code>constexpr</code>函数在调用时若传入的实参值是编译器已知的，则会产出编译期结果。</li><li>比起非<code>constexpr</code>对象或<code>constexpr</code>函数而言，<code>constexpr</code>对象或是<code>constexpr</code>函数可以用在一个作用域更广的语境中。</li></ul>]]>
    </content>
    <id>https://devilsean.github.io/posts/62728637.html</id>
    <link href="https://devilsean.github.io/posts/62728637.html"/>
    <published>2026-03-15T12:00:00.000Z</published>
    <summary>区分 constexpr 作用于对象和函数时的不同含义，说明编译期求值、const 与运行期求值之间的关系。</summary>
    <title>Item15：尽可能使用constexpr</title>
    <updated>2026-10-30T11:04:53.940Z</updated>
  </entry>
  <entry>
    <author>
      <name>Sean</name>
    </author>
    <category term="Effective Modern C++" scheme="https://devilsean.github.io/categories/Effective-Modern-C/"/>
    <category term="3. 转向现代C++" scheme="https://devilsean.github.io/categories/Effective-Modern-C/3-%E8%BD%AC%E5%90%91%E7%8E%B0%E4%BB%A3C/"/>
    <category term="C++" scheme="https://devilsean.github.io/tags/C/"/>
    <category term="线程安全" scheme="https://devilsean.github.io/tags/%E7%BA%BF%E7%A8%8B%E5%AE%89%E5%85%A8/"/>
    <content>
      <![CDATA[<p><code>const</code> 成员函数承诺 “不修改对象核心逻辑状态”，但常因<code>mutable</code>修饰的辅助变量（如多项式根的缓存<code>rootVals</code>、缓存有效标记<code>rootsAreValid</code>），在执行时修改这些变量；</p><p>多线程同时调用该 <code>const</code> 函数时，会出现 “不同线程读写同一块内存” 的<strong>数据竞争</strong>，导致程序行为未定义（即使逻辑上是 “读操作”）。</p><h2 id="线程安全的解决方案"><a href="#线程安全的解决方案" class="headerlink" title="线程安全的解决方案"></a>线程安全的解决方案</h2><h3 id="1-基础方案：使用std-mutex"><a href="#1-基础方案：使用std-mutex" class="headerlink" title="1. 基础方案：使用std::mutex"></a>1. 基础方案：使用<code>std::mutex</code></h3><ul><li><p>适用场景：需要同步<strong>多个变量 &#x2F; 多个操作</strong>（如缓存值 + 缓存有效标记），保证操作的原子性；</p></li><li><p>实现要点：</p><ul><li>将<code>std::mutex</code>声明为<code>mutable</code>（因 <code>const</code> 成员函数中 <code>mutex</code> 被视为 <code>const</code> 对象，而锁 &#x2F; 解锁操作会修改 <code>mutex</code> 状态）；</li><li>用<code>std::lock_guard</code>自动加锁 &#x2F; 解锁，避免手动解锁遗漏；</li></ul></li><li><p>副作用：<code>std::mutex</code>不可拷贝 &#x2F; 移动，导致包含它的类也不可拷贝 &#x2F; 移动；</p></li><li><p>示例：多项式根缓存的线程安全实现（加锁后保证缓存计算 &#x2F; 赋值的原子性）。</p></li></ul><h3 id="2-轻量方案：使用std-atomic"><a href="#2-轻量方案：使用std-atomic" class="headerlink" title="2. 轻量方案：使用std::atomic"></a>2. 轻量方案：使用<code>std::atomic</code></h3><ul><li><p>适用场景：仅需同步<strong>单个变量</strong>（如函数调用次数统计），开销比 <code>mutex</code> 小；</p></li><li><p>实现要点：将统计变量声明为<code>std::atomic&lt;类型&gt;</code>且<code>mutable</code>，其操作是原子的（不可分割），避免数据竞争；</p></li><li><p>副作用：<code>std::atomic</code>同样不可拷贝 &#x2F; 移动，导致包含它的类不可拷贝 &#x2F; 移动；</p></li><li><p>坑点：<strong>绝对不能用于多个变量的同步</strong>（如缓存值 + 缓存有效标记）：</p><ul><li>先赋值缓存值、后标记有效：会导致多线程重复计算（违背缓存初衷）；</li><li>先标记有效、后赋值缓存值：会导致线程读取到未赋值的脏数据。</li></ul></li></ul><table><thead><tr><th>方案</th><th>适用场景</th><th>优势</th><th>限制</th></tr></thead><tbody><tr><td><code>std::mutex</code></td><td>多个变量 &#x2F; 操作的同步</td><td>保证操作原子性</td><td>开销略大、类不可拷贝 &#x2F; 移动</td></tr><tr><td><code>std::atomic</code></td><td>单个变量的同步</td><td>开销小、操作原子化</td><td>无法同步多个变量 &#x2F; 操作</td></tr></tbody></table><h2 id="例外情况：无需保证线程安全的场景"><a href="#例外情况：无需保证线程安全的场景" class="headerlink" title="例外情况：无需保证线程安全的场景"></a>例外情况：无需保证线程安全的场景</h2><p>若能绝对保证 <code>const</code> 成员函数<strong>永远不会在多线程环境下调用</strong>（如仅用于单线程独占的类），可省略 <code>mutex/atomic</code>，避免资源开销和类拷贝 &#x2F; 移动的限制；但这种场景越来越少见，优先保证线程安全。</p><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><ul><li>保证<code>const</code>成员函数的线程安全性，除非可以确信它们不会用在并发语境中。</li><li>运用<code>std::atomic</code>类型的变量会比运用互斥量提供更好的性能，但前者仅适用对单个变量或内存区域的操作。</li></ul>]]>
    </content>
    <id>https://devilsean.github.io/posts/cafb5bbb.html</id>
    <link href="https://devilsean.github.io/posts/cafb5bbb.html"/>
    <published>2026-03-15T12:00:00.000Z</published>
    <summary>说明 const 成员函数通过 mutable 缓存修改状态时仍可能产生数据竞争，并给出 mutex 与 atomic 的同步方案。</summary>
    <title>Item16：让const成员函数线程安全</title>
    <updated>2026-10-30T11:04:53.956Z</updated>
  </entry>
  <entry>
    <author>
      <name>Sean</name>
    </author>
    <category term="Effective Modern C++" scheme="https://devilsean.github.io/categories/Effective-Modern-C/"/>
    <category term="3. 转向现代C++" scheme="https://devilsean.github.io/categories/Effective-Modern-C/3-%E8%BD%AC%E5%90%91%E7%8E%B0%E4%BB%A3C/"/>
    <category term="C++" scheme="https://devilsean.github.io/tags/C/"/>
    <category term="初始化" scheme="https://devilsean.github.io/tags/%E5%88%9D%E5%A7%8B%E5%8C%96/"/>
    <content>
      <![CDATA[<h2 id="初始化的基本方式"><a href="#初始化的基本方式" class="headerlink" title="初始化的基本方式"></a>初始化的基本方式</h2><p>指定初始化的方式包括使用小括号、使用等号，或是使用大括号：</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="type">int</span> <span class="title">x</span><span class="params">(<span class="number">0</span>)</span></span>;</span><br><span class="line"><span class="type">int</span> y = <span class="number">0</span>;</span><br><span class="line"><span class="type">int</span> z&#123;<span class="number">0</span>&#125;;</span><br></pre></td></tr></table></figure><p>很多情况下使用一个等号和一对大括号也是可以的：（处理方式等同于只有大括号）</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="type">int</span> z = &#123;<span class="number">0</span>&#125;;</span><br></pre></td></tr></table></figure><p>对于用户定义的类型，等号的初始化方式实际并没有发生赋值：</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">Widget w1; <span class="comment">// 调用的是默认构造函数</span></span><br><span class="line">Widget w2 = w1; <span class="comment">// 井非赋值，调用的是复制构造函数</span></span><br><span class="line">w1 = w2;  <span class="comment">// 并非赋值．调用的是复制赋值运算符</span></span><br></pre></td></tr></table></figure><p>C++11引入了统一初始化：使用大括号进行初始化。</p><ol><li>使用大括号来制定容器的初始内容：</li></ol><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">std: :vector&lt;<span class="type">int</span>&gt; v&#123; <span class="number">1</span>, <span class="number">3</span>, <span class="number">5</span> &#125;; <span class="comment">// v 的初始内容为1、3、5</span></span><br></pre></td></tr></table></figure><ol><li>为非静态成员指定默认初始化值（也可以使用“&#x3D;”，但是<code>()</code>不行）：</li></ol><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">Widget</span> </span><br><span class="line">&#123; </span><br><span class="line"> ... </span><br><span class="line"><span class="keyword">private</span>: </span><br><span class="line"> <span class="type">int</span> x&#123; <span class="number">0</span> &#125;; <span class="comment">// 可行，x 的默认值为 0 </span></span><br><span class="line"> <span class="type">int</span> y = <span class="number">0</span>; <span class="comment">// 也可行 </span></span><br><span class="line"> <span class="function"><span class="type">int</span> <span class="title">z</span><span class="params">(<span class="number">0</span>)</span></span>; <span class="comment">// 不可行! </span></span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><blockquote><p>不可复制对象（如<code>std::atomic</code>类型的对象）可以使用大括号和小括号来进行复制，但是不能使用“&#x3D;”。</p></blockquote><p>由上可以看出，<strong>只有大括号初始化适用于所有场景</strong>。</p><h2 id="大括号初始化的特性"><a href="#大括号初始化的特性" class="headerlink" title="大括号初始化的特性"></a>大括号初始化的特性</h2><ol><li><strong>禁止内建类型之间进行隐式窄化类型转换</strong>。如果大括号内的表达式无法保证能够采用进行初始化的对象来表达，则代码不能通过编译。<br>如：</li></ol><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="type">double</span> x, y, z; </span><br><span class="line"><span class="type">int</span> sum1&#123; x + y + z &#125;; <span class="comment">// 错误!double类型之和可能无法用int表达</span></span><br></pre></td></tr></table></figure><ol><li>对于<strong>C++最令人苦恼的解析语法</strong>（MVP：任何能够解析为声明的都要解析为声明。最苦恼——程序员本来想要以默认方式构造一个对象，却不小心声明了一个函数）免疫。<br>如：</li></ol><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="function">Widget <span class="title">w1</span><span class="params">()</span></span>; <span class="comment">// 解析为函数声明（MVP问题） </span></span><br><span class="line">Widget w2&#123;&#125;; <span class="comment">// 明确为对象初始化，无歧义</span></span><br></pre></td></tr></table></figure><h2 id="存在的缺陷"><a href="#存在的缺陷" class="headerlink" title="存在的缺陷"></a>存在的缺陷</h2><h3 id="1-auto变量的异常推导"><a href="#1-auto变量的异常推导" class="headerlink" title="1. auto变量的异常推导"></a>1. <code>auto</code>变量的异常推导</h3><p><code>auto</code>结合<code>{}</code>初始化时，变量类型会被推导为<code>std::initializer_list</code>（而非直觉类型）：</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">auto</span> a = <span class="number">10</span>;          <span class="comment">// 推导为int（符合直觉）</span></span><br><span class="line"><span class="keyword">auto</span> b&#123;<span class="number">10</span>&#125;;           <span class="comment">// C++11/14：std::initializer_list&lt;int&gt;；C++17后：int</span></span><br><span class="line"><span class="keyword">auto</span> c = &#123;<span class="number">10</span>, <span class="number">20</span>&#125;;    <span class="comment">// 始终推导为std::initializer_list&lt;int&gt;</span></span><br></pre></td></tr></table></figure><h3 id="2-构造函数重载决议的优先级规则"><a href="#2-构造函数重载决议的优先级规则" class="headerlink" title="2. 构造函数重载决议的优先级规则"></a>2. 构造函数重载决议的优先级规则</h3><ul><li><strong>无<code>std::initializer_list</code>形参时</strong>：<code>()</code>和<code>{}</code>初始化结果一致：</li></ul><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">Widget</span> &#123; </span><br><span class="line"><span class="keyword">public</span>:  </span><br><span class="line">    <span class="built_in">Widget</span>(<span class="type">int</span> i, <span class="type">bool</span> b);      <span class="comment">// 无std::initializer_list形参</span></span><br><span class="line">    <span class="built_in">Widget</span>(<span class="type">int</span> i, <span class="type">double</span> d);    </span><br><span class="line">    …</span><br><span class="line">&#125;;</span><br><span class="line"><span class="function">Widget <span class="title">w1</span><span class="params">(<span class="number">10</span>, <span class="literal">true</span>)</span></span>;            <span class="comment">// 调用第一个构造函数</span></span><br><span class="line">Widget w2&#123;<span class="number">10</span>, <span class="literal">true</span>&#125;;            <span class="comment">// 调用第一个构造函数</span></span><br><span class="line"><span class="function">Widget <span class="title">w3</span><span class="params">(<span class="number">10</span>, <span class="number">5.0</span>)</span></span>;             <span class="comment">// 调用第二个构造函数</span></span><br><span class="line">Widget w4&#123;<span class="number">10</span>, <span class="number">5.0</span>&#125;;             <span class="comment">// 调用第二个构造函数</span></span><br></pre></td></tr></table></figure><ul><li><strong>有<code>std::initializer_list</code>形参时</strong>：<code>{}</code>会<strong>强制优先匹配</strong>该构造函数（即使普通构造更匹配）：</li></ul><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">Widget</span> &#123; </span><br><span class="line"><span class="keyword">public</span>:  </span><br><span class="line">    <span class="built_in">Widget</span>(<span class="type">int</span> i, <span class="type">bool</span> b);      </span><br><span class="line">    <span class="built_in">Widget</span>(<span class="type">int</span> i, <span class="type">double</span> d);    </span><br><span class="line">    <span class="built_in">Widget</span>(std::initializer_list&lt;<span class="type">long</span> <span class="type">double</span>&gt; il); <span class="comment">// 新增</span></span><br><span class="line">    …</span><br><span class="line">&#125;; </span><br><span class="line"><span class="function">Widget <span class="title">w1</span><span class="params">(<span class="number">10</span>, <span class="literal">true</span>)</span></span>;    <span class="comment">// 调用第一个构造函数（()不受影响）</span></span><br><span class="line">Widget w2&#123;<span class="number">10</span>, <span class="literal">true</span>&#125;;    <span class="comment">// 调用std::initializer_list构造（10/true转long double）</span></span><br><span class="line"><span class="function">Widget <span class="title">w3</span><span class="params">(<span class="number">10</span>, <span class="number">5.0</span>)</span></span>;     <span class="comment">// 调用第二个构造函数（()不受影响）</span></span><br><span class="line">Widget w4&#123;<span class="number">10</span>, <span class="number">5.0</span>&#125;;     <span class="comment">// 调用std::initializer_list构造（10/5.0转long double）</span></span><br></pre></td></tr></table></figure><ul><li><strong>优先级极端性</strong>：即便<code>std::initializer_list</code>构造无法调用，编译器仍会优先尝试（失败才回退普通决议）：</li></ul><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">Widget</span> &#123; </span><br><span class="line"><span class="keyword">public</span>: </span><br><span class="line">    <span class="built_in">Widget</span>(<span class="type">int</span> i, <span class="type">bool</span> b);      </span><br><span class="line">    <span class="built_in">Widget</span>(<span class="type">int</span> i, <span class="type">double</span> d);    </span><br><span class="line">    <span class="built_in">Widget</span>(std::initializer_list&lt;<span class="type">bool</span>&gt; il); <span class="comment">// 元素类型为bool</span></span><br><span class="line">    …</span><br><span class="line">&#125;;</span><br><span class="line">Widget w&#123;<span class="number">10</span>, <span class="number">5.0</span>&#125;;              <span class="comment">// 错误！10/5.0转bool属于窄化转换，&#123;&#125;禁止</span></span><br></pre></td></tr></table></figure><ul><li><strong>空<code>{}</code>的边界规则</strong>：空<code>{}</code>表示 “无实参”，优先调用默认构造（而非空<code>std::initializer_list</code>）；需传空列表时需嵌套括号：</li></ul><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">Widget</span> &#123; </span><br><span class="line"><span class="keyword">public</span>:  </span><br><span class="line">    <span class="built_in">Widget</span>();                                   <span class="comment">// 默认构造</span></span><br><span class="line">    <span class="built_in">Widget</span>(std::initializer_list&lt;<span class="type">int</span>&gt; il);      <span class="comment">// std::initializer_list构造</span></span><br><span class="line">    …</span><br><span class="line">&#125;;</span><br><span class="line">Widget w1;                      <span class="comment">// 调用默认构造</span></span><br><span class="line">Widget w2&#123;&#125;;                    <span class="comment">// 调用默认构造</span></span><br><span class="line"><span class="function">Widget <span class="title">w3</span><span class="params">()</span></span>;                    <span class="comment">// MVP！声明函数</span></span><br><span class="line"><span class="function">Widget <span class="title">w4</span><span class="params">(&#123;&#125;)</span></span>;                  <span class="comment">// 调用std::initializer_list构造（空列表）</span></span><br><span class="line">Widget w5&#123;&#123;&#125;&#125;;                  <span class="comment">// 同上</span></span><br></pre></td></tr></table></figure><h3 id="3-std-vector的-和-初始化结果差异巨大"><a href="#3-std-vector的-和-初始化结果差异巨大" class="headerlink" title="3. std::vector的()和{}初始化结果差异巨大"></a>3. <code>std::vector</code>的<code>()</code>和<code>{}</code>初始化结果差异巨大</h3><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="function">std::vector&lt;<span class="type">int</span>&gt; <span class="title">v1</span><span class="params">(<span class="number">10</span>, <span class="number">20</span>)</span></span>;    <span class="comment">// 非std::initializer_list构造：10个元素，值均为20</span></span><br><span class="line">std::vector&lt;<span class="type">int</span>&gt; v2&#123;<span class="number">10</span>, <span class="number">20</span>&#125;;    <span class="comment">// std::initializer_list构造：2个元素，值为10、20</span></span><br></pre></td></tr></table></figure><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><ul><li>大括号初始化可以应用的语境最为宽泛，可以阻止隐式窄化类型转换，还对最令人苦恼之解析语法免疫。</li><li>在构造函数重载决议期间，只要有任何可能，大括号初始化物就会与带有<code>std::initializer_list</code>类型的形参相匹配，即使其他重载版本有着貌似更加匹配的形参表。</li><li>对于数值类型的<code>std::vector</code>来说使用大括号初始化和小括号初始化会造成巨大的不同</li><li>在模板内容进行对象创建时，到底应该使用小括号还是大括号会成为一个棘手问题。</li></ul>]]>
    </content>
    <id>https://devilsean.github.io/posts/ff707fc2.html</id>
    <link href="https://devilsean.github.io/posts/ff707fc2.html"/>
    <published>2026-03-15T12:00:00.000Z</published>
    <summary>梳理小括号、等号与大括号初始化的差异，重点说明统一初始化、initializer_list 匹配和窄化转换带来的坑。</summary>
    <title>Item7：在创建对象时注意区分()和{}</title>
    <updated>2026-10-30T11:04:53.987Z</updated>
  </entry>
  <entry>
    <author>
      <name>Sean</name>
    </author>
    <category term="Effective Modern C++" scheme="https://devilsean.github.io/categories/Effective-Modern-C/"/>
    <category term="3. 转向现代C++" scheme="https://devilsean.github.io/categories/Effective-Modern-C/3-%E8%BD%AC%E5%90%91%E7%8E%B0%E4%BB%A3C/"/>
    <category term="C++" scheme="https://devilsean.github.io/tags/C/"/>
    <category term="noexcept" scheme="https://devilsean.github.io/tags/noexcept/"/>
    <content>
      <![CDATA[<p> C++11 的<code>noexcept</code>是替代 C++98 废弃异常说明的核心特性，作为函数接口的关键组成部分，仅当<strong>确定函数永远不会抛出异常</strong>时声明；其核心价值是让编译器生成更高效的目标代码，同时为调用者 &#x2F; STL 提供明确的异常安全承诺，且不可为了加<code>noexcept</code>扭曲函数实现，大多数函数因 “异常中立” 无需声明。</p><h2 id="noexcept的核心价值"><a href="#noexcept的核心价值" class="headerlink" title="noexcept的核心价值"></a><code>noexcept</code>的核心价值</h2><h3 id="性能层面：编译器最大化优化代码"><a href="#性能层面：编译器最大化优化代码" class="headerlink" title="性能层面：编译器最大化优化代码"></a>性能层面：编译器最大化优化代码</h3><ul><li><strong>逻辑</strong>：<code>noexcept</code>承诺 “函数异常传播时可直接终止程序”，编译器无需保证调用栈完整展开、局部对象按构造反序析构，因此可剔除冗余代码，生成更精简高效的机器码；</li><li><strong>对比差异</strong>：<code>noexcept</code>函数的优化度远高于 C++98 的<code>throw()</code>或无异常声明的函数（后两者需预留栈展开的冗余代码）；</li></ul><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// noexcept函数：编译器无冗余准备，极致优化</span></span><br><span class="line"><span class="function"><span class="type">void</span> <span class="title">moveWidget</span><span class="params">(Widget&amp;&amp; w)</span> <span class="keyword">noexcept</span> </span>&#123;</span><br><span class="line">    <span class="comment">// 无栈展开、析构顺序记录的冗余代码，机器码更精简</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// throw()函数：需预留栈展开准备，优化有限（C++17已废弃）</span></span><br><span class="line"><span class="function"><span class="type">void</span> <span class="title">moveWidget</span><span class="params">(Widget&amp;&amp; w)</span> <span class="title">throw</span><span class="params">()</span> </span>&#123;</span><br><span class="line">    <span class="comment">// 含局部对象析构顺序记录、栈可展开维护的冗余代码</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 无声明函数：默认按“可能抛异常”处理，优化空间极小</span></span><br><span class="line"><span class="function"><span class="type">void</span> <span class="title">moveWidget</span><span class="params">(Widget&amp;&amp; w)</span> </span>&#123;</span><br><span class="line">    <span class="comment">// 同throw()，冗余代码多，执行效率低</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="语义层面：作为接口承诺，支撑-STL-核心逻辑"><a href="#语义层面：作为接口承诺，支撑-STL-核心逻辑" class="headerlink" title="语义层面：作为接口承诺，支撑 STL 核心逻辑"></a>语义层面：作为接口承诺，支撑 STL 核心逻辑</h3><ul><li><strong>逻辑</strong>：<code>noexcept</code>是函数接口的关键属性（与<code>const</code>同等重要），调用者 &#x2F; STL 会依赖该承诺判断是否安全使用高性能逻辑；</li><li><strong>支撑场景</strong>：</li></ul><ol><li>移动操作（构造 &#x2F; 赋值）：STL 容器仅当移动操作<code>noexcept</code>时，才会用 “移动” 替代 “拷贝”（避免移动抛异常破坏异常安全）；</li></ol><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 移动构造加noexcept：STL容器优先用移动（高效且安全）</span></span><br><span class="line"><span class="keyword">class</span> <span class="title class_">Widget</span> &#123;</span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line">    <span class="built_in">Widget</span>(Widget&amp;&amp; other) <span class="keyword">noexcept</span> &#123;</span><br><span class="line">        <span class="comment">// 转移资源，无异常风险</span></span><br><span class="line">    &#125;</span><br><span class="line">&#125;;</span><br><span class="line"></span><br><span class="line">std::vector&lt;Widget&gt; vec;</span><br><span class="line">Widget w;</span><br><span class="line">vec.<span class="built_in">push_back</span>(std::<span class="built_in">move</span>(w)); <span class="comment">// 容器调用移动构造（因noexcept）</span></span><br></pre></td></tr></table></figure><ol><li><code>swap</code>函数：高层次结构（数组、<code>std::pair</code>）的<code>swap</code>是否<code>noexcept</code>依赖底层元素，加<code>noexcept</code>可让整个层级受益；</li></ol><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 自定义Widget的swap加noexcept，支撑数组/pair的swap优化</span></span><br><span class="line"><span class="keyword">class</span> <span class="title class_">Widget</span> &#123;</span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line">    <span class="function"><span class="type">void</span> <span class="title">swap</span><span class="params">(Widget&amp; other)</span> <span class="keyword">noexcept</span> </span>&#123;</span><br><span class="line">        <span class="comment">// 交换资源，无异常风险</span></span><br><span class="line">    &#125;</span><br><span class="line">&#125;;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 数组swap的noexcept依赖Widget::swap</span></span><br><span class="line">Widget arr1[<span class="number">5</span>], arr2[<span class="number">5</span>];</span><br><span class="line">std::<span class="built_in">swap</span>(arr1, arr2); <span class="comment">// 因Widget::swap noexcept，数组swap也noexcept</span></span><br></pre></td></tr></table></figure><ol><li>析构 &#x2F; 内存释放函数：C++11 默认隐式<code>noexcept</code>，无需显式声明，保证内存管理和对象销毁安全；</li></ol><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">Widget</span> &#123;</span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line">    ~<span class="built_in">Widget</span>() &#123; <span class="comment">// 默认隐式noexcept，无需显式写</span></span><br><span class="line">        <span class="comment">// 销毁资源，无异常风险</span></span><br><span class="line">    &#125;</span><br><span class="line">&#125;;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 内存释放函数默认noexcept</span></span><br><span class="line"><span class="function"><span class="type">void</span> <span class="keyword">operator</span> <span class="title">delete</span><span class="params">(<span class="type">void</span>* ptr)</span> </span>&#123;</span><br><span class="line">    <span class="comment">// 释放内存，无异常风险</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="noexcept的适用-不适用场景"><a href="#noexcept的适用-不适用场景" class="headerlink" title="noexcept的适用 &#x2F; 不适用场景"></a><code>noexcept</code>的适用 &#x2F; 不适用场景</h2><h3 id="必加noexcept的场景"><a href="#必加noexcept的场景" class="headerlink" title="必加noexcept的场景"></a>必加<code>noexcept</code>的场景</h3><ul><li>移动构造 &#x2F; 移动赋值函数（STL 性能优化的核心）；</li><li><code>swap</code>函数（STL 算法核心，层级依赖其<code>noexcept</code>属性）；</li><li>析构函数、<code>operator delete/delete[]</code>（默认隐式<code>noexcept</code>，无需显式声明）；</li><li>无前置条件的宽泛契约函数，且确定永不抛异常；</li></ul><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 宽泛契约（无前置条件），且确定不抛异常，加noexcept</span></span><br><span class="line"><span class="function"><span class="type">int</span> <span class="title">calculateSum</span><span class="params">(<span class="type">int</span> a, <span class="type">int</span> b)</span> <span class="keyword">noexcept</span> </span>&#123;</span><br><span class="line">    <span class="keyword">return</span> a + b;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="绝对不加noexcept的场景"><a href="#绝对不加noexcept的场景" class="headerlink" title="绝对不加noexcept的场景"></a>绝对不加<code>noexcept</code>的场景</h3><ul><li>异常中立函数：自身不抛异常，但内部调用可能抛（加了会导致异常传播时程序直接终止）；</li></ul><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 异常中立函数：内部调用可能抛的函数，不加noexcept</span></span><br><span class="line"><span class="function"><span class="type">void</span> <span class="title">processData</span><span class="params">(<span class="type">const</span> std::string&amp; data)</span> </span>&#123;</span><br><span class="line">    <span class="comment">// 调用可能抛异常的函数（如new、文件操作）</span></span><br><span class="line">    <span class="type">char</span>* buf = <span class="keyword">new</span> <span class="type">char</span>[data.<span class="built_in">size</span>()]; </span><br><span class="line">    <span class="comment">// 允许异常传播到上层处理，不加noexcept</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><ul><li>严格契约函数：有前置条件，检查条件时无法抛异常调试；</li></ul><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 严格契约（前置条件：s.length()&lt;=32），不加noexcept</span></span><br><span class="line"><span class="function"><span class="type">void</span> <span class="title">processShortString</span><span class="params">(<span class="type">const</span> std::string&amp; s)</span> </span>&#123;</span><br><span class="line">    <span class="comment">// 调试时可抛异常提示前置条件违反，故不加noexcept</span></span><br><span class="line">    <span class="keyword">if</span> (s.<span class="built_in">length</span>() &gt; <span class="number">32</span>) &#123;</span><br><span class="line">        <span class="keyword">throw</span> std::<span class="built_in">invalid_argument</span>(<span class="string">&quot;string too long&quot;</span>);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><ul><li>为加<code>noexcept</code>扭曲实现（如捕获所有异常转状态码，增加代码复杂度）；</li></ul><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 反例：为加noexcept扭曲实现，不推荐</span></span><br><span class="line"><span class="function"><span class="type">int</span> <span class="title">riskyFunc</span><span class="params">()</span> <span class="keyword">noexcept</span> </span>&#123;</span><br><span class="line">    <span class="keyword">try</span> &#123;</span><br><span class="line">        <span class="comment">// 调用可能抛异常的函数</span></span><br><span class="line">        <span class="keyword">return</span> <span class="built_in">callUnsafeFunc</span>();</span><br><span class="line">    &#125; <span class="built_in">catch</span> (...) &#123;</span><br><span class="line">        <span class="comment">// 捕获所有异常，返回错误码，增加复杂度</span></span><br><span class="line">        <span class="keyword">return</span> <span class="number">-1</span>; </span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><ul><li>无法保证长期永不抛异常的函数（<code>noexcept</code>是接口承诺，修改会影响所有调用者）；</li></ul><h2 id="注意点"><a href="#注意点" class="headerlink" title="注意点"></a>注意点</h2><ul><li>编译器不校验<code>noexcept</code>与函数实现的一致性：<code>noexcept</code>函数可调用无<code>noexcept</code>但实际不抛的函数（如 C 库、旧 C++98 函数），编译器不报错；</li></ul><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// noexcept函数调用无noexcept但实际不抛的C库函数，编译器允许</span></span><br><span class="line"><span class="function"><span class="type">void</span> <span class="title">processString</span><span class="params">(<span class="type">const</span> <span class="type">char</span>* str)</span> <span class="keyword">noexcept</span> </span>&#123;</span><br><span class="line">    std::<span class="built_in">strlen</span>(str); <span class="comment">// C库函数，无noexcept但实际不抛</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><ul><li>正确性优先于优化：永远不要为了加<code>noexcept</code>牺牲函数逻辑正确和接口稳定；</li><li>大多数函数是异常中立的，无需加<code>noexcept</code>；</li></ul><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><ul><li>仅当确定函数永不抛异常时，才声明<code>noexcept</code>，其是函数接口的重要组成部分。</li><li><code>noexcept</code>函数可被编译器极致优化，且是 STL 移动语义、<code>swap</code>函数高效安全使用的核心支撑。</li><li>移动操作、<code>swap</code>函数是<code>noexcept</code>的核心适用场景，异常中立函数、严格契约函数等绝对不加<code>noexcept</code>。</li></ul>]]>
    </content>
    <id>https://devilsean.github.io/posts/4b0a629a.html</id>
    <link href="https://devilsean.github.io/posts/4b0a629a.html"/>
    <published>2026-03-15T12:00:00.000Z</published>
    <summary>解释 noexcept 对代码生成、STL 选择和异常安全的价值，强调只在确定不抛异常时声明，避免扭曲实现。</summary>
    <title>Item14：只要函数不抛出异常就加上noexcept声明</title>
    <updated>2026-10-30T11:04:53.925Z</updated>
  </entry>
  <entry>
    <author>
      <name>Sean</name>
    </author>
    <category term="Effective Modern C++" scheme="https://devilsean.github.io/categories/Effective-Modern-C/"/>
    <category term="5. 右值引用、移动语义和完美转发" scheme="https://devilsean.github.io/categories/Effective-Modern-C/5-%E5%8F%B3%E5%80%BC%E5%BC%95%E7%94%A8%E3%80%81%E7%A7%BB%E5%8A%A8%E8%AF%AD%E4%B9%89%E5%92%8C%E5%AE%8C%E7%BE%8E%E8%BD%AC%E5%8F%91/"/>
    <category term="C++" scheme="https://devilsean.github.io/tags/C/"/>
    <category term="move" scheme="https://devilsean.github.io/tags/move/"/>
    <category term="forward" scheme="https://devilsean.github.io/tags/forward/"/>
    <content>
      <![CDATA[<h2 id="两者的本质是编译期类型转换，运行时无任何操作"><a href="#两者的本质是编译期类型转换，运行时无任何操作" class="headerlink" title="两者的本质是编译期类型转换，运行时无任何操作"></a>两者的本质是编译期类型转换，运行时无任何操作</h2><ul><li><code>std::move</code><strong>不移动任何数据</strong>，<code>std::forward</code><strong>不转发任何数据</strong>；</li><li>它们都是<strong>函数模板</strong>，仅在<strong>编译期</strong>对参数做<strong>值类别转换</strong>（左值→右值，或条件性转换），运行时不生成任何可执行代码（一字节都没有）；</li><li>两者的核心作用都是改变参数的<strong>值类别</strong>（左值 &#x2F; 右值），而非操作数据本身。</li></ul><h2 id="std-move：无条件将参数转为右值（但不保证能移动）"><a href="#std-move：无条件将参数转为右值（但不保证能移动）" class="headerlink" title="std::move：无条件将参数转为右值（但不保证能移动）"></a><code>std::move</code>：无条件将参数转为右值（但不保证能移动）</h2><h4 id="1-本质拆解"><a href="#1-本质拆解" class="headerlink" title="1. 本质拆解"></a>1. 本质拆解</h4><p>接收一个<strong>通用引用</strong>参数，通过<code>static_cast</code>无条件转换为<strong>非引用类型的右值引用</strong>（用<code>std::remove_reference</code>确保不会因为 T 是左值引用而变成左值），最终返回一个右值。</p><p>简单说：<code>std::move(x)</code>的唯一作用是把<code>x</code>标记为<strong>右值</strong>，告诉编译器 “这个对象可以被移动”—— 但这只是 “提议”，不是 “强制”。</p><h4 id="2-const-对象用-std-move-会-“伪移动”（实际是拷贝）"><a href="#2-const-对象用-std-move-会-“伪移动”（实际是拷贝）" class="headerlink" title="2. const 对象用 std::move 会 “伪移动”（实际是拷贝）"></a>2. const 对象用 std::move 会 “伪移动”（实际是拷贝）</h4><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 错误示范：给想移动的对象加了const</span></span><br><span class="line"><span class="keyword">class</span> <span class="title class_">Annotation</span> &#123;</span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line">    <span class="function"><span class="keyword">explicit</span> <span class="title">Annotation</span><span class="params">(<span class="type">const</span> std::string text)</span></span></span><br><span class="line"><span class="function">    : value(std::move(text)) // 看似移动，实际是拷贝</span></span><br><span class="line"><span class="function">    &#123;</span>&#125;</span><br><span class="line"><span class="keyword">private</span>:</span><br><span class="line">    std::string value;</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><ul><li><code>text</code>是<code>const std::string</code>，<code>std::move(text)</code>会把它转为<code>const std::string</code> 的右值；</li><li><code>std::string</code>的移动构造函数要求<code>non-const</code> 的右值引用（因为移动会修改原对象），无法匹配这个 <code>const</code> 右值；</li><li>最终编译器会调用<code>std::string</code>的<strong>拷贝构造函数</strong>（拷贝构造接受<code>const string&amp;</code>，可绑定到 const 右值）；</li><li>结论：想移动的对象<strong>不要声明为 const</strong>——<code>const</code> 对象的 <code>move</code> 请求会悄无声息变成拷贝。</li></ul><h4 id="3-结论"><a href="#3-结论" class="headerlink" title="3. 结论"></a>3. 结论</h4><p><code>std::move</code>能保证的只有 “把参数转为右值”，但不保证 “这个对象能被移动”（比如 <code>const</code> 对象、不支持移动的类型）；它的命名是为了语义清晰（“我想把这个对象转为右值，准备移动它”），而非实际执行移动。</p><h2 id="std-forward：有条件的转换（仅当参数原是右值时，才转为右值）"><a href="#std-forward：有条件的转换（仅当参数原是右值时，才转为右值）" class="headerlink" title="std::forward：有条件的转换（仅当参数原是右值时，才转为右值）"></a><code>std::forward</code>：有条件的转换（仅当参数原是右值时，才转为右值）</h2><h4 id="1-本质拆解-1"><a href="#1-本质拆解-1" class="headerlink" title="1. 本质拆解"></a>1. 本质拆解</h4><p><code>std::forward</code>是条件性转换，场景是：<strong>通用引用的模板函数中，转发参数到其他函数，保留参数原本的左 &#x2F; 右值属性</strong>。</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 重载函数：分别处理左值/右值Widget</span></span><br><span class="line"><span class="function"><span class="type">void</span> <span class="title">process</span><span class="params">(<span class="type">const</span> Widget&amp; lvalArg)</span></span>;</span><br><span class="line"><span class="function"><span class="type">void</span> <span class="title">process</span><span class="params">(Widget&amp;&amp; rvalArg)</span></span>;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 模板函数：转发param到process，保留param的左/右值属性</span></span><br><span class="line"><span class="function"><span class="keyword">template</span>&lt;<span class="keyword">typename</span> T&gt;</span></span><br><span class="line"><span class="function"><span class="type">void</span> <span class="title">logAndProcess</span><span class="params">(T&amp;&amp; param)</span> </span>&#123;</span><br><span class="line">    <span class="comment">// ... 日志逻辑</span></span><br><span class="line">    <span class="built_in">process</span>(std::forward&lt;T&gt;(param)); <span class="comment">// 关键：forward保留值类别</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><ul><li>函数形参<code>param</code>本身是<strong>左值</strong>（所有函数形参都是左值，哪怕它绑定的是右值）；</li><li>如果直接调用<code>process(param)</code>，无论传入的是左值还是右值，都会调用<code>process</code>的左值重载；</li><li><code>std::forward&lt;T&gt;(param)</code>的作用：<strong>仅当传入 logAndProcess 的原始实参是右值时，才把 param 转为右值；若原始实参是左值，则保持左值</strong>—— 最终让 <code>process</code> 调用匹配原始实参的类型。</li></ul><h4 id="2-实现原理"><a href="#2-实现原理" class="headerlink" title="2. 实现原理"></a>2. 实现原理</h4><p><code>std::forward</code>能判断 “参数原本是左值还是右值”，是因为模板参数<code>T</code>中保存了原始实参的值类别信息，<code>forward</code>通过解析<code>T</code>来决定是否转换。</p><h2 id="语义不同，而非功能不可替代"><a href="#语义不同，而非功能不可替代" class="headerlink" title="语义不同，而非功能不可替代"></a>语义不同，而非功能不可替代</h2><h4 id="1-技术上：std-forward-可以替代-std-move"><a href="#1-技术上：std-forward-可以替代-std-move" class="headerlink" title="1. 技术上：std::forward 可以替代 std::move"></a>1. 技术上：std::forward 可以替代 std::move</h4><p>因为<code>std::move</code>是 “无条件转右值”，而<code>std::forward</code>是 “条件转右值”—— 技术上可以用<code>forward</code>实现 move 的效果（比如<code>std::forward&lt;std::string&gt;(rhs.s)</code>等价于<code>std::move(rhs.s)</code>），但<strong>绝不推荐</strong>。</p><h4 id="2-为什么要分开设计？"><a href="#2-为什么要分开设计？" class="headerlink" title="2. 为什么要分开设计？"></a>2. 为什么要分开设计？</h4><ul><li><p><code>std::move</code>更简洁：无需传递模板类型参数，代码更少、出错概率更低；</p></li><li><p>语义更清晰：</p><ul><li><code>std::move(x)</code>：明确表达 “我想把 x 转为右值，准备移动它”；</li><li><code>std::forward(x)</code>：明确表达 “我想原样转发 x，保留它的左 &#x2F; 右值属性”；</li></ul></li><li><p>分开命名让代码意图一目了然，符合 “代码即注释” 的设计原则。</p></li></ul><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><ol><li><code>std::move</code>和<code>std::forward</code>都是<strong>编译期类型转换工具</strong>，运行时无任何操作 ——move 不移动数据，forward 不转发数据；</li><li><code>std::move</code><strong>无条件</strong>将参数转为右值，但 const 对象用 move 会触发拷贝而非移动；</li><li><code>std::forward</code><strong>有条件</strong>转换：仅当参数原始是右值时，才转为右值（核心用于通用引用的参数转发）；</li><li>技术上 forward 可替代 move，但 move 更简洁、语义更明确（标记 “准备移动”），forward 语义是 “原样转发”，两者各司其职。</li></ol>]]>
    </content>
    <id>https://devilsean.github.io/posts/ad06c595.html</id>
    <link href="https://devilsean.github.io/posts/ad06c595.html"/>
    <published>2026-03-15T12:00:00.000Z</published>
    <summary>说明 std::move 与 std::forward 只是编译期类型转换，不移动也不转发数据，并解释两者的使用边界。</summary>
    <title>Item23：理解move和forward</title>
    <updated>2026-10-30T11:04:54.121Z</updated>
  </entry>
</feed>
