EffectiveModernCppChinese/5.RRefMovSemPerfForw/item28.html
2022-11-25 05:43:31 +00:00

323 lines
29 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<!DOCTYPE HTML>
<html lang="zh" class="sidebar-visible no-js light">
<head>
<!-- Book generated using mdBook -->
<meta charset="UTF-8">
<title>Item 28:理解引用折叠 - Effective Modern C++</title>
<!-- Custom HTML head -->
<meta content="text/html; charset=utf-8" http-equiv="Content-Type">
<meta name="description" content="">
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="theme-color" content="#ffffff" />
<link rel="icon" href="../favicon.svg">
<link rel="shortcut icon" href="../favicon.png">
<link rel="stylesheet" href="../css/variables.css">
<link rel="stylesheet" href="../css/general.css">
<link rel="stylesheet" href="../css/chrome.css">
<link rel="stylesheet" href="../css/print.css" media="print">
<!-- Fonts -->
<link rel="stylesheet" href="../FontAwesome/css/font-awesome.css">
<link rel="stylesheet" href="../fonts/fonts.css">
<!-- Highlight.js Stylesheets -->
<link rel="stylesheet" href="../highlight.css">
<link rel="stylesheet" href="../tomorrow-night.css">
<link rel="stylesheet" href="../ayu-highlight.css">
<!-- Custom theme stylesheets -->
<!-- MathJax -->
<script async type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/mathjax/2.7.1/MathJax.js?config=TeX-AMS-MML_HTMLorMML"></script>
</head>
<body>
<!-- Provide site root to javascript -->
<script type="text/javascript">
var path_to_root = "../";
var default_theme = window.matchMedia("(prefers-color-scheme: dark)").matches ? "navy" : "light";
</script>
<!-- Work around some values being stored in localStorage wrapped in quotes -->
<script type="text/javascript">
try {
var theme = localStorage.getItem('mdbook-theme');
var sidebar = localStorage.getItem('mdbook-sidebar');
if (theme.startsWith('"') && theme.endsWith('"')) {
localStorage.setItem('mdbook-theme', theme.slice(1, theme.length - 1));
}
if (sidebar.startsWith('"') && sidebar.endsWith('"')) {
localStorage.setItem('mdbook-sidebar', sidebar.slice(1, sidebar.length - 1));
}
} catch (e) { }
</script>
<!-- Set the theme before any content is loaded, prevents flash -->
<script type="text/javascript">
var theme;
try { theme = localStorage.getItem('mdbook-theme'); } catch(e) { }
if (theme === null || theme === undefined) { theme = default_theme; }
var html = document.querySelector('html');
html.classList.remove('no-js')
html.classList.remove('light')
html.classList.add(theme);
html.classList.add('js');
</script>
<!-- Hide / unhide sidebar before it is displayed -->
<script type="text/javascript">
var html = document.querySelector('html');
var sidebar = 'hidden';
if (document.body.clientWidth >= 1080) {
try { sidebar = localStorage.getItem('mdbook-sidebar'); } catch(e) { }
sidebar = sidebar || 'visible';
}
html.classList.remove('sidebar-visible');
html.classList.add("sidebar-" + sidebar);
</script>
<nav id="sidebar" class="sidebar" aria-label="Table of contents">
<div class="sidebar-scrollbox">
<ol class="chapter"><li class="chapter-item expanded "><a href="../Introduction.html">简介</a></li><li class="chapter-item expanded "><div>第一章 类型推导</div></li><li><ol class="section"><li class="chapter-item expanded "><a href="../1.DeducingTypes/item1.html">Item 1:理解模板类型推导</a></li><li class="chapter-item expanded "><a href="../1.DeducingTypes/item2.html">Item 2:理解auto类型推导</a></li><li class="chapter-item expanded "><a href="../1.DeducingTypes/item3.html">Item 3:理解decltype</a></li><li class="chapter-item expanded "><a href="../1.DeducingTypes/item4.html">Item 4:学会查看类型推导结果</a></li></ol></li><li class="chapter-item expanded "><div>第二章 auto</div></li><li><ol class="section"><li class="chapter-item expanded "><a href="../2.Auto/item5.html">Item 5:优先考虑auto而非显式类型声明</a></li><li class="chapter-item expanded "><a href="../2.Auto/item6.html">Item 6:auto推导若非己愿使用显式类型初始化惯用法</a></li></ol></li><li class="chapter-item expanded "><div>第三章 移步现代C++</div></li><li><ol class="section"><li class="chapter-item expanded "><a href="../3.MovingToModernCpp/item7.html">Item 7:区别使用()和{}创建对象</a></li><li class="chapter-item expanded "><a href="../3.MovingToModernCpp/item8.html">Item 8:优先考虑nullptr而非0和NULL</a></li><li class="chapter-item expanded "><a href="../3.MovingToModernCpp/item9.html">Item 9:优先考虑别名声明而非typedefs</a></li><li class="chapter-item expanded "><a href="../3.MovingToModernCpp/item10.html">Item 10:优先考虑限域枚举而非未限域枚举</a></li><li class="chapter-item expanded "><a href="../3.MovingToModernCpp/item11.html">Item 11:优先考虑使用deleted函数而非使用未定义的私有声明</a></li><li class="chapter-item expanded "><a href="../3.MovingToModernCpp/item12.html">Item 12:使用override声明重载函数</a></li><li class="chapter-item expanded "><a href="../3.MovingToModernCpp/item13.html">Item 13:优先考虑const_iterator而非iterator</a></li><li class="chapter-item expanded "><a href="../3.MovingToModernCpp/item14.html">Item 14:如果函数不抛出异常请使用noexcept</a></li><li class="chapter-item expanded "><a href="../3.MovingToModernCpp/item15.html">Item 15:尽可能的使用constexpr</a></li><li class="chapter-item expanded "><a href="../3.MovingToModernCpp/item16.html">Item 16:让const成员函数线程安全</a></li><li class="chapter-item expanded "><a href="../3.MovingToModernCpp/item17.html">Item 17:理解特殊成员函数函数的生成</a></li></ol></li><li class="chapter-item expanded "><div>第四章 智能指针</div></li><li><ol class="section"><li class="chapter-item expanded "><a href="../4.SmartPointers/item18.html">Item 18:对于独占资源使用std::unique_ptr</a></li><li class="chapter-item expanded "><a href="../4.SmartPointers/item19.html">Item 19:对于共享资源使用std::shared_ptr</a></li><li class="chapter-item expanded "><a href="../4.SmartPointers/item20.html">Item 20:当std::shared_ptr可能悬空时使用std::weak_ptr</a></li><li class="chapter-item expanded "><a href="../4.SmartPointers/item21.html">Item 21:优先考虑使用std::make_unique和std::make_shared而非new</a></li><li class="chapter-item expanded "><a href="../4.SmartPointers/item22.html">Item 22:当使用Pimpl惯用法请在实现文件中定义特殊成员函数</a></li></ol></li><li class="chapter-item expanded "><div>第五章 右值引用,移动语义,完美转发</div></li><li><ol class="section"><li class="chapter-item expanded "><a href="../5.RRefMovSemPerfForw/item23.html">Item 23:理解std::move和std::forward</a></li><li class="chapter-item expanded "><a href="../5.RRefMovSemPerfForw/item24.html">Item 24:区别通用引用和右值引用</a></li><li class="chapter-item expanded "><a href="../5.RRefMovSemPerfForw/item25.html">Item 25:对于右值引用使用std::move对于通用引用使用std::forward</a></li><li class="chapter-item expanded "><a href="../5.RRefMovSemPerfForw/item26.html">Item 26:避免重载通用引用</a></li><li class="chapter-item expanded "><a href="../5.RRefMovSemPerfForw/item27.html">Item 27:熟悉重载通用引用的替代品</a></li><li class="chapter-item expanded "><a href="../5.RRefMovSemPerfForw/item28.html" class="active">Item 28:理解引用折叠</a></li><li class="chapter-item expanded "><a href="../5.RRefMovSemPerfForw/item29.html">Item 29:认识移动操作的缺点</a></li><li class="chapter-item expanded "><a href="../5.RRefMovSemPerfForw/item30.html">Item 30:熟悉完美转发失败的情况</a></li></ol></li><li class="chapter-item expanded "><div>第六章 Lambda表达式</div></li><li><ol class="section"><li class="chapter-item expanded "><a href="../6.LambdaExpressions/item31.html">Item 31:避免使用默认捕获模式</a></li><li class="chapter-item expanded "><a href="../6.LambdaExpressions/item32.html">Item 32:使用初始化捕获来移动对象到闭包中</a></li><li class="chapter-item expanded "><a href="../6.LambdaExpressions/item33.html">Item 33:对于std::forward的auto&&形参使用decltype</a></li><li class="chapter-item expanded "><a href="../6.LambdaExpressions/item34.html">Item 34:优先考虑lambda表达式而非std::bind</a></li></ol></li><li class="chapter-item expanded "><div>第七章 并发API</div></li><li><ol class="section"><li class="chapter-item expanded "><a href="../7.TheConcurrencyAPI/Item35.html">Item 35:优先考虑基于任务的编程而非基于线程的编程</a></li><li class="chapter-item expanded "><a href="../7.TheConcurrencyAPI/item36.html">Item 36:如果有异步的必要请指定std::launch::threads</a></li><li class="chapter-item expanded "><a href="../7.TheConcurrencyAPI/item37.html">Item 37:从各个方面使得std::threads unjoinable</a></li><li class="chapter-item expanded "><a href="../7.TheConcurrencyAPI/item38.html">Item 38:关注不同线程句柄析构行为</a></li><li class="chapter-item expanded "><a href="../7.TheConcurrencyAPI/item39.html">Item 39:考虑对于单次事件通信使用void</a></li><li class="chapter-item expanded "><a href="../7.TheConcurrencyAPI/item40.html">Item 40:对于并发使用std::atomicvolatile用于特殊内存区</a></li></ol></li><li class="chapter-item expanded "><div>第八章 微调</div></li><li><ol class="section"><li class="chapter-item expanded "><a href="../8.Tweaks/item41.html">Item 41:对于那些可移动总是被拷贝的形参使用传值方式</a></li><li class="chapter-item expanded "><a href="../8.Tweaks/item42.html">Item 42:考虑就地创建而非插入</a></li></ol></li></ol>
</div>
<div id="sidebar-resize-handle" class="sidebar-resize-handle"></div>
</nav>
<div id="page-wrapper" class="page-wrapper">
<div class="page">
<div id="menu-bar-hover-placeholder"></div>
<div id="menu-bar" class="menu-bar sticky bordered">
<div class="left-buttons">
<button id="sidebar-toggle" class="icon-button" type="button" title="Toggle Table of Contents" aria-label="Toggle Table of Contents" aria-controls="sidebar">
<i class="fa fa-bars"></i>
</button>
<button id="theme-toggle" class="icon-button" type="button" title="Change theme" aria-label="Change theme" aria-haspopup="true" aria-expanded="false" aria-controls="theme-list">
<i class="fa fa-paint-brush"></i>
</button>
<ul id="theme-list" class="theme-popup" aria-label="Themes" role="menu">
<li role="none"><button role="menuitem" class="theme" id="light">Light (default)</button></li>
<li role="none"><button role="menuitem" class="theme" id="rust">Rust</button></li>
<li role="none"><button role="menuitem" class="theme" id="coal">Coal</button></li>
<li role="none"><button role="menuitem" class="theme" id="navy">Navy</button></li>
<li role="none"><button role="menuitem" class="theme" id="ayu">Ayu</button></li>
</ul>
<button id="search-toggle" class="icon-button" type="button" title="Search. (Shortkey: s)" aria-label="Toggle Searchbar" aria-expanded="false" aria-keyshortcuts="S" aria-controls="searchbar">
<i class="fa fa-search"></i>
</button>
</div>
<h1 class="menu-title">Effective Modern C++</h1>
<div class="right-buttons">
<a href="../print.html" title="Print this book" aria-label="Print this book">
<i id="print-button" class="fa fa-print"></i>
</a>
<a href="https://github.com/CnTransGroup/EffectiveModernCppChinese" title="Git repository" aria-label="Git repository">
<i id="git-repository-button" class="fa fa-github"></i>
</a>
</div>
</div>
<div id="search-wrapper" class="hidden">
<form id="searchbar-outer" class="searchbar-outer">
<input type="search" id="searchbar" name="searchbar" placeholder="Search this book ..." aria-controls="searchresults-outer" aria-describedby="searchresults-header">
</form>
<div id="searchresults-outer" class="searchresults-outer hidden">
<div id="searchresults-header" class="searchresults-header"></div>
<ul id="searchresults">
</ul>
</div>
</div>
<!-- Apply ARIA attributes after the sidebar and the sidebar toggle button are added to the DOM -->
<script type="text/javascript">
document.getElementById('sidebar-toggle').setAttribute('aria-expanded', sidebar === 'visible');
document.getElementById('sidebar').setAttribute('aria-hidden', sidebar !== 'visible');
Array.from(document.querySelectorAll('#sidebar a')).forEach(function(link) {
link.setAttribute('tabIndex', sidebar === 'visible' ? 0 : -1);
});
</script>
<div id="content" class="content">
<main>
<h2 id="条款二十八理解引用折叠"><a class="header" href="#条款二十八理解引用折叠">条款二十八:理解引用折叠</a></h2>
<p><strong>Item 28: Understand reference collapsing</strong></p>
<p><a href="../5.RRefMovSemPerfForw/item23.html">Item23</a>中指出,当实参传递给模板函数时,被推导的模板形参<code>T</code>根据实参是左值还是右值来编码。但是那条款并没有提到只有当实参被用来实例化通用引用形参时,上述推导才会发生,但是有充分的理由忽略这一点:因为通用引用是<a href="../5.RRefMovSemPerfForw/item24.html">Item24</a>中才提到。回过头来看,对通用引用和左值/右值编码的观察意味着对于这个模板,</p>
<pre><code class="language-cpp">template&lt;typename T&gt;
void func(T&amp;&amp; param);
</code></pre>
<p>不管传给param的实参是左值还是右值模板形参<code>T</code>都会编码。</p>
<p>编码机制是简单的。当左值实参被传入时,<code>T</code>被推导为左值引用。当右值被传入时,<code>T</code>被推导为非引用。(请注意不对称性:左值被编码为左值引用,右值被编码为<strong>非引用</strong>。)因此:</p>
<pre><code class="language-cpp">Widget widgetFactory(); //返回右值的函数
Widget w; //一个变量(左值)
func(w); //用左值调用funcT被推导为Widget&amp;
func(widgetFactory()); //用右值调用funcT被推导为Widget
</code></pre>
<p>上面的两种<code>func</code>调用中,<code>Widget</code>被传入,因为一个是左值,一个是右值,模板形参<code>T</code>被推导为不同的类型。正如我们很快看到的,这决定了通用引用成为左值还是右值,也是<code>std::forward</code>的工作基础。</p>
<p>在我们更加深入<code>std::forward</code>和通用引用之前必须明确在C++中引用的引用是非法的。不知道你是否尝试过下面的写法,编译器会报错:</p>
<pre><code class="language-cpp">int x;
auto&amp; &amp; rx = x; //错误!不能声明引用的引用
</code></pre>
<p>考虑下,如果一个左值传给接受通用引用的模板函数会发生什么:</p>
<pre><code class="language-cpp">template&lt;typename T&gt;
void func(T&amp;&amp; param); //同之前一样
func(w); //用左值调用funcT被推导为Widget&amp;
</code></pre>
<p>如果我们用<code>T</code>推导出来的类型(即<code>Widget&amp;</code>)初始化模板,会得到:</p>
<pre><code class="language-cpp">void func(Widget&amp; &amp;&amp; param);
</code></pre>
<p>引用的引用!但是编译器没有报错。我们从<a href="../5.RRefMovSemPerfForw/item24.html">Item24</a>中了解到因为通用引用<code>param</code>被传入一个左值,所以<code>param</code>的类型应该为左值引用,但是编译器如何把<code>T</code>推导的类型带入模板变成如下的结果,也就是最终的函数签名?</p>
<pre><code class="language-cpp">void func(Widget&amp; param);
</code></pre>
<p>答案是<strong>引用折叠</strong><em>reference collapsing</em>)。是的,禁止<strong></strong>声明引用的引用,但是<strong>编译器</strong>会在特定的上下文中产生这些,模板实例化就是其中一种情况。当编译器生成引用的引用时,引用折叠指导下一步发生什么。</p>
<p>存在两种类型的引用(左值和右值),所以有四种可能的引用组合(左值的左值,左值的右值,右值的右值,右值的左值)。如果一个上下文中允许引用的引用存在(比如,模板的实例化),引用根据规则<strong>折叠</strong>为单个引用:</p>
<blockquote>
<p>如果任一引用为左值引用,则结果为左值引用。否则(即,如果引用都是右值引用),结果为右值引用。</p>
</blockquote>
<p>在我们上面的例子中,将推导类型<code>Widget&amp;</code>替换进模板<code>func</code>会产生对左值引用的右值引用,然后引用折叠规则告诉我们结果就是左值引用。</p>
<p>引用折叠是<code>std::forward</code>工作的一种关键机制。就像<a href="../5.RRefMovSemPerfForw/item25.html">Item25</a>中解释的一样,<code>std::forward</code>应用在通用引用参数上,所以经常能看到这样使用:</p>
<pre><code class="language-cpp">template&lt;typename T&gt;
void f(T&amp;&amp; fParam)
{
… //做些工作
someFunc(std::forward&lt;T&gt;(fParam)); //转发fParam到someFunc
}
</code></pre>
<p>因为<code>fParam</code>是通用引用,我们知道类型参数<code>T</code>的类型根据<code>f</code>被传入实参(即用来实例化<code>fParam</code>的表达式)是左值还是右值来编码。<code>std::forward</code>的作用是当且仅当传给<code>f</code>的实参为右值时,即<code>T</code>为非引用类型,才将<code>fParam</code>(左值)转化为一个右值。</p>
<p><code>std::forward</code>可以这样实现:</p>
<pre><code class="language-cpp">template&lt;typename T&gt; //在std命名空间
T&amp;&amp; forward(typename
remove_reference&lt;T&gt;::type&amp; param)
{
return static_cast&lt;T&amp;&amp;&gt;(param);
}
</code></pre>
<p>这不是标准库版本的实现(忽略了一些接口描述),但是为了理解<code>std::forward</code>的行为,这些差异无关紧要。</p>
<p>假设传入到<code>f</code>的实参是<code>Widget</code>的左值类型。<code>T</code>被推导为<code>Widget&amp;</code>,然后调用<code>std::forward</code>将实例化为<code>std::forward&lt;Widget&amp;&gt;</code><code>Widget&amp;</code>带入到上面的<code>std::forward</code>的实现中:</p>
<pre><code class="language-cpp">Widget&amp; &amp;&amp; forward(typename
remove_reference&lt;Widget&amp;&gt;::type&amp; param)
{ return static_cast&lt;Widget&amp; &amp;&amp;&gt;(param); }
</code></pre>
<p><code>std::remove_reference&lt;Widget&amp;&gt;::type</code>这个<em>type trait</em>产生<code>Widget</code>(查看<a href="../3.MovingToModernCpp/item9.html">Item9</a>),所以<code>std::forward</code>成为:</p>
<pre><code class="language-cpp">Widget&amp; &amp;&amp; forward(Widget&amp; param)
{ return static_cast&lt;Widget&amp; &amp;&amp;&gt;(param); }
</code></pre>
<p>根据引用折叠规则,返回值和强制转换可以化简,最终版本的<code>std::forward</code>调用就是:</p>
<pre><code class="language-cpp">Widget&amp; forward(Widget&amp; param)
{ return static_cast&lt;Widget&amp;&gt;(param); }
</code></pre>
<p>正如你所看到的,当左值实参被传入到函数模板<code>f</code>时,<code>std::forward</code>被实例化为接受和返回左值引用。内部的转换不做任何事,因为<code>param</code>的类型已经是<code>Widget&amp;</code>,所以转换没有影响。左值实参传入<code>std::forward</code>会返回左值引用。通过定义,左值引用就是左值,因此将左值传递给<code>std::forward</code>会返回左值,就像期待的那样。</p>
<p>现在假设一下,传递给<code>f</code>的实参是一个<code>Widget</code>的右值。在这个例子中,<code>f</code>的类型参数<code>T</code>的推导类型就是<code>Widget</code><code>f</code>内部的<code>std::forward</code>调用因此为<code>std::forward&lt;Widget&gt;</code><code>std::forward</code>实现中把<code>T</code>换为<code>Widget</code>得到:</p>
<pre><code class="language-cpp">Widget&amp;&amp; forward(typename
remove_reference&lt;Widget&gt;::type&amp; param)
{ return static_cast&lt;Widget&amp;&amp;&gt;(param); }
</code></pre>
<p><code>std::remove_reference</code>引用到非引用类型<code>Widget</code>上还是相同的类型(<code>Widget</code>),所以<code>std::forward</code>变成:</p>
<pre><code class="language-cpp">Widget&amp;&amp; forward(Widget&amp; param)
{ return static_cast&lt;Widget&amp;&amp;&gt;(param); }
</code></pre>
<p>这里没有引用的引用,所以不需要引用折叠,这就是<code>std::forward</code>的最终实例化版本。</p>
<p>从函数返回的右值引用被定义为右值,因此在这种情况下,<code>std::forward</code>会将<code>f</code>的形参<code>fParam</code>(左值)转换为右值。最终结果是,传递给<code>f</code>的右值参数将作为右值转发给<code>someFunc</code>,正是想要的结果。</p>
<p>在C++14中<code>std::remove_reference_t</code>的存在使得实现变得更简洁:</p>
<pre><code class="language-cpp">template&lt;typename T&gt; //C++14仍然在std命名空间
T&amp;&amp; forward(remove_reference_t&lt;T&gt;&amp; param)
{
return static_cast&lt;T&amp;&amp;&gt;(param);
}
</code></pre>
<p>引用折叠发生在四种情况下。第一,也是最常见的就是模板实例化。第二,是<code>auto</code>变量的类型生成,具体细节类似于模板,因为<code>auto</code>变量的类型推导基本与模板类型推导雷同(参见<a href="../1.DeducingTypes/item2.html">Item2</a>)。考虑本条款前面的例子:</p>
<pre><code class="language-cpp">Widget widgetFactory(); //返回右值的函数
Widget w; //一个变量(左值)
func(w); //用左值调用funcT被推导为Widget&amp;
func(widgetFactory()); //用又值调用funcT被推导为Widget
</code></pre>
<p>在auto的写法中规则是类似的。声明</p>
<pre><code class="language-cpp">auto&amp;&amp; w1 = w;
</code></pre>
<p>用一个左值初始化<code>w1</code>,因此为<code>auto</code>推导出类型<code>Widget&amp;</code>。把<code>Widget&amp;</code>代回<code>w1</code>声明中的<code>auto</code>里,产生了引用的引用,</p>
<pre><code class="language-cpp">Widget&amp; &amp;&amp; w1 = w;
</code></pre>
<p>应用引用折叠规则,就是</p>
<pre><code class="language-cpp">Widget&amp; w1 = w
</code></pre>
<p>结果就是<code>w1</code>是一个左值引用。</p>
<p>另一方面,这个声明,</p>
<pre><code class="language-cpp">auto&amp;&amp; w2 = widgetFactory();
</code></pre>
<p>使用右值初始化<code>w2</code>,为<code>auto</code>推导出非引用类型<code>Widget</code>。把<code>Widget</code>代入<code>auto</code>得到:</p>
<pre><code class="language-cpp">Widget&amp;&amp; w2 = widgetFactory()
</code></pre>
<p>没有引用的引用,这就是最终结果,<code>w2</code>是个右值引用。</p>
<p>现在我们真正理解了<a href="../5.RRefMovSemPerfForw/item24.html">Item24</a>中引入的通用引用。通用引用不是一种新的引用,它实际上是满足以下两个条件下的右值引用:</p>
<ul>
<li><strong>类型推导区分左值和右值</strong><code>T</code>类型的左值被推导为<code>T&amp;</code>类型,<code>T</code>类型的右值被推导为<code>T</code></li>
<li><strong>发生引用折叠</strong></li>
</ul>
<p>通用引用的概念是有用的,因为它使你不必一定意识到引用折叠的存在,从直觉上推导左值和右值的不同类型,在凭直觉把推导的类型代入到它们出现的上下文中之后应用引用折叠规则。</p>
<p>我说了有四种情况会发生引用折叠,但是只讨论了两种:模板实例化和<code>auto</code>的类型生成。第三种情况是<code>typedef</code>和别名声明的产生和使用中(参见<a href="../3.MovingToModernCpp/item9.html">Item9</a>)。如果,在创建或者评估<code>typedef</code>过程中出现了引用的引用,则引用折叠就会起作用。举例子来说,假设我们有一个<code>Widget</code>的类模板,该模板具有右值引用类型的嵌入式<code>typedef</code></p>
<pre><code class="language-cpp">template&lt;typename T&gt;
class Widget {
public:
typedef T&amp;&amp; RvalueRefToT;
};
</code></pre>
<p>假设我们使用左值引用实例化<code>Widget</code></p>
<pre><code class="language-cpp">Widget&lt;int&amp;&gt; w;
</code></pre>
<p><code>Widget</code>模板中把<code>T</code>替换为<code>int&amp;</code>得到:</p>
<pre><code class="language-cpp">typedef int&amp; &amp;&amp; RvalueRefToT;
</code></pre>
<p>引用折叠就会发挥作用:</p>
<pre><code class="language-cpp">typedef int&amp; RvalueRefToT;
</code></pre>
<p>这清楚表明我们为<code>typedef</code>选择的名字可能不是我们希望的那样:当使用左值引用类型实例化<code>Widget</code>时,<code>RvalueRefToT</code><strong>左值引用</strong><code>typedef</code></p>
<p>最后一种引用折叠发生的情况是,<code>decltype</code>使用的情况。如果在分析<code>decltype</code>期间,出现了引用的引用,引用折叠规则就会起作用(关于<code>decltype</code>,参见<a href="../1.DeducingTypes/item3.html">Item3</a></p>
<p><strong>请记住:</strong></p>
<ul>
<li>引用折叠发生在四种情况下:模板实例化,<code>auto</code>类型推导,<code>typedef</code>与别名声明的创建和使用,<code>decltype</code></li>
<li>当编译器在引用折叠环境中生成了引用的引用时,结果就是单个引用。有左值引用折叠结果就是左值引用,否则就是右值引用。</li>
<li>通用引用就是在特定上下文的右值引用,上下文是通过类型推导区分左值还是右值,并且发生引用折叠的那些地方。</li>
</ul>
</main>
<nav class="nav-wrapper" aria-label="Page navigation">
<!-- Mobile navigation buttons -->
<a rel="prev" href="../5.RRefMovSemPerfForw/item27.html" class="mobile-nav-chapters previous" title="Previous chapter" aria-label="Previous chapter" aria-keyshortcuts="Left">
<i class="fa fa-angle-left"></i>
</a>
<a rel="next" href="../5.RRefMovSemPerfForw/item29.html" class="mobile-nav-chapters next" title="Next chapter" aria-label="Next chapter" aria-keyshortcuts="Right">
<i class="fa fa-angle-right"></i>
</a>
<div style="clear: both"></div>
</nav>
</div>
</div>
<nav class="nav-wide-wrapper" aria-label="Page navigation">
<a rel="prev" href="../5.RRefMovSemPerfForw/item27.html" class="nav-chapters previous" title="Previous chapter" aria-label="Previous chapter" aria-keyshortcuts="Left">
<i class="fa fa-angle-left"></i>
</a>
<a rel="next" href="../5.RRefMovSemPerfForw/item29.html" class="nav-chapters next" title="Next chapter" aria-label="Next chapter" aria-keyshortcuts="Right">
<i class="fa fa-angle-right"></i>
</a>
</nav>
</div>
<script type="text/javascript">
window.playground_copyable = true;
</script>
<script src="../elasticlunr.min.js" type="text/javascript" charset="utf-8"></script>
<script src="../mark.min.js" type="text/javascript" charset="utf-8"></script>
<script src="../searcher.js" type="text/javascript" charset="utf-8"></script>
<script src="../clipboard.min.js" type="text/javascript" charset="utf-8"></script>
<script src="../highlight.js" type="text/javascript" charset="utf-8"></script>
<script src="../book.js" type="text/javascript" charset="utf-8"></script>
<!-- Custom JS scripts -->
</body>
</html>