EffectiveModernCppChinese/6.LambdaExpressions/item33.html
2022-11-25 05:43:31 +00:00

251 lines
24 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 33:对于std::forward的auto&amp;&amp;形参使用decltype - 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">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" class="active">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="条款三十三对auto形参使用decltype以stdforward它们"><a class="header" href="#条款三十三对auto形参使用decltype以stdforward它们">条款三十三:对<code>auto&amp;&amp;</code>形参使用<code>decltype</code><code>std::forward</code>它们</a></h2>
<p><strong>Item 33: Use <code>decltype</code> on <code>auto&amp;&amp;</code> parameters to <code>std::forward</code> them</strong></p>
<p><strong>泛型<em>lambda</em></strong><em>generic lambdas</em>是C++14中最值得期待的特性之一——因为在<em>lambda</em>的形参中可以使用<code>auto</code>关键字。这个特性的实现是非常直截了当的:即在闭包类中的<code>operator()</code>函数是一个函数模版。例如存在这么一个<em>lambda</em></p>
<pre><code class="language-c++">auto f = [](auto x){ return func(normalize(x)); };
</code></pre>
<p>对应的闭包类中的函数调用操作符看来就变成这样:</p>
<pre><code class="language-c++">class SomeCompilerGeneratedClassName {
public:
template&lt;typename T&gt; //auto返回类型见条款3
auto operator()(T x) const
{ return func(normalize(x)); }
… //其他闭包类功能
};
</code></pre>
<p>在这个样例中,<em>lambda</em>对变量<code>x</code>做的唯一一件事就是把它转发给函数<code>normalize</code>。如果函数<code>normalize</code>对待左值右值的方式不一样,这个<em>lambda</em>的实现方式就不大合适了,因为即使传递到<em>lambda</em>的实参是一个右值,<em>lambda</em>传递进<code>normalize</code>的总是一个左值(形参<code>x</code>)。</p>
<p>实现这个<em>lambda</em>的正确方式是把<code>x</code>完美转发给函数<code>normalize</code>。这样做需要对代码做两处修改。首先,<code>x</code>需要改成通用引用(见<a href="../5.RRefMovSemPerfForw/item24.html">Item24</a>),其次,需要使用<code>std::forward</code><code>x</code>转发到函数<code>normalize</code>(见<a href="../5.RRefMovSemPerfForw/item25.html">Item25</a>)。理论上,这都是小改动:</p>
<pre><code class="language-c++">auto f = [](auto&amp;&amp; x)
{ return func(normalize(std::forward&lt;???&gt;(x))); };
</code></pre>
<p>在理论和实际之间存在一个问题:你应该传递给<code>std::forward</code>的什么类型,即确定我在上面写的<code>???</code>该是什么。</p>
<p>一般来说,当你在使用完美转发时,你是在一个接受类型参数为<code>T</code>的模版函数里,所以你可以写<code>std::forward&lt;T&gt;</code>。但在泛型<em>lambda</em>中,没有可用的类型参数<code>T</code>。在<em>lambda</em>生成的闭包里,模版化的<code>operator()</code>函数中的确有一个<code>T</code>,但在<em>lambda</em>里却无法直接使用它,所以也没什么用。</p>
<p><a href="../5.RRefMovSemPerfForw/item28.html">Item28</a>解释过如果一个左值实参被传给通用引用的形参,那么形参类型会变成左值引用。传递的是右值,形参就会变成右值引用。这意味着在这个<em>lambda</em>中,可以通过检查形参<code>x</code>的类型来确定传递进来的实参是一个左值还是右值,<code>decltype</code>就可以实现这样的效果(见<a href="../1.DeducingTypes/item3.html">Item3</a>)。传递给<em>lambda</em>的是一个左值,<code>decltype(x)</code>就能产生一个左值引用;如果传递的是一个右值,<code>decltype(x)</code>就会产生右值引用。</p>
<p><a href="../5.RRefMovSemPerfForw/item28.html">Item28</a>也解释过在调用<code>std::forward</code>时,惯例决定了类型实参是左值引用时来表明要传进左值,类型实参是非引用就表明要传进右值。在前面的<em>lambda</em>中,如果<code>x</code>绑定的是一个左值,<code>decltype(x)</code>就能产生一个左值引用。这符合惯例。然而如果<code>x</code>绑定的是一个右值,<code>decltype(x)</code>就会产生右值引用,而不是常规的非引用。</p>
<p>再看一下<a href="../5.RRefMovSemPerfForw/item28.html">Item28</a>中关于<code>std::forward</code>的C++14实现</p>
<pre><code class="language-c++">template&lt;typename T&gt; //在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>Widget</code>类型的右值时,它会使用<code>Widget</code>类型(即非引用类型)来实例化<code>std::forward</code>,然后产生以下的函数:</p>
<pre><code class="language-c++">Widget&amp;&amp; forward(Widget&amp; param) //当T是Widget时的std::forward实例
{
return static_cast&lt;Widget&amp;&amp;&gt;(param);
}
</code></pre>
<p>思考一下如果用户代码想要完美转发一个<code>Widget</code>类型的右值,但没有遵守规则将<code>T</code>指定为非引用类型,而是将<code>T</code>指定为右值引用,这会发生什么。也就是,思考将<code>T</code>换成<code>Widget&amp;&amp;</code>会如何。在<code>std::forward</code>实例化、应用了<code>std::remove_reference_t</code>后,引用折叠之前,<code>std::forward</code>看起来像这样:</p>
<pre><code class="language-c++">Widget&amp;&amp; &amp;&amp; forward(Widget&amp; param) //当T是Widget&amp;&amp;时的std::forward实例
{ //(引用折叠之前)
return static_cast&lt;Widget&amp;&amp; &amp;&amp;&gt;(param);
}
</code></pre>
<p>应用了引用折叠之后(右值引用的右值引用变成单个右值引用),代码会变成:</p>
<pre><code class="language-c++">Widget&amp;&amp; forward(Widget&amp; param) //当T是Widget&amp;&amp;时的std::forward实例
{ //(引用折叠之后)
return static_cast&lt;Widget&amp;&amp;&gt;(param);
}
</code></pre>
<p>对比这个实例和用<code>Widget</code>设置<code>T</code>去实例化产生的结果,它们完全相同。表明用右值引用类型和用非引用类型去初始化<code>std::forward</code>产生的相同的结果。</p>
<p>那是一个很好的消息,因为当传递给<em>lambda</em>形参<code>x</code>的是一个右值实参时,<code>decltype(x)</code>可以产生一个右值引用。前面已经确认过,把一个左值传给<em>lambda</em>时,<code>decltype(x)</code>会产生一个可以传给<code>std::forward</code>的常规类型。而现在也验证了对于右值,把<code>decltype(x)</code>产生的类型传递给<code>std::forward</code>是非传统的,不过它产生的实例化结果与传统类型相同。所以无论是左值还是右值,把<code>decltype(x)</code>传递给<code>std::forward</code>都能得到我们想要的结果,因此<em>lambda</em>的完美转发可以写成:</p>
<pre><code class="language-c++">auto f =
[](auto&amp;&amp; param)
{
return
func(normalize(std::forward&lt;decltype(param)&gt;(param)));
};
</code></pre>
<p>再加上6个点就可以让我们的<em>lambda</em>完美转发接受多个形参了因为C++14中的<em>lambda</em>也可以是可变形参的:</p>
<pre><code class="language-c++">auto f =
[](auto&amp;&amp;... params)
{
return
func(normalize(std::forward&lt;decltype(params)&gt;(params)...));
};
</code></pre>
<p><strong>请记住:</strong></p>
<ul>
<li><code>auto&amp;&amp;</code>形参使用<code>decltype</code><code>std::forward</code>它们。</li>
</ul>
</main>
<nav class="nav-wrapper" aria-label="Page navigation">
<!-- Mobile navigation buttons -->
<a rel="prev" href="../6.LambdaExpressions/item32.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="../6.LambdaExpressions/item34.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="../6.LambdaExpressions/item32.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="../6.LambdaExpressions/item34.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>