EffectiveModernCppChinese/6.LambdaExpressions/item34.html
2022-06-30 02:23:03 +00:00

380 lines
34 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 34: Prefer lambdas to std::bind - 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">Introduction</a></li><li class="chapter-item expanded "><div>Chapter 1. Deducing Types</div></li><li><ol class="section"><li class="chapter-item expanded "><a href="../1.DeducingTypes/item1.html">Item 1: Understanding template type deduction</a></li><li class="chapter-item expanded "><a href="../1.DeducingTypes/item2.html">Item 2: Understand auto type deduction</a></li><li class="chapter-item expanded "><a href="../1.DeducingTypes/item3.html">Item 3: Understand decltype</a></li><li class="chapter-item expanded "><a href="../1.DeducingTypes/item4.html">Item 4: Know how to view deduced types</a></li></ol></li><li class="chapter-item expanded "><div>Chapter 2. auto</div></li><li><ol class="section"><li class="chapter-item expanded "><a href="../2.Auto/item5.html">Item 5: Prefer auto to explicit type declarations</a></li><li class="chapter-item expanded "><a href="../2.Auto/item6.html">Item 6: Use the explicitly typed initializer idiom when auto deduces undesired types</a></li></ol></li><li class="chapter-item expanded "><div>Chapter 3. Moving to Modern C++</div></li><li><ol class="section"><li class="chapter-item expanded "><a href="../3.MovingToModernCpp/item7.html">Item 7: Distinguish between () and {} when creating objects</a></li><li class="chapter-item expanded "><a href="../3.MovingToModernCpp/item8.html">Item 8: Prefer nullptr to 0 or NULL</a></li><li class="chapter-item expanded "><a href="../3.MovingToModernCpp/item9.html">Item 9: Prefer alias declarations to typedefs</a></li><li class="chapter-item expanded "><a href="../3.MovingToModernCpp/item10.html">Item 10: Prefer scoped enums to unscoped enums</a></li><li class="chapter-item expanded "><a href="../3.MovingToModernCpp/item11.html">Item 11: Prefer deleted functions to private undefined ones</a></li><li class="chapter-item expanded "><a href="../3.MovingToModernCpp/item12.html">Item 12: Declare overriding functions override</a></li><li class="chapter-item expanded "><a href="../3.MovingToModernCpp/item13.html">Item 13: Prefer const_iterators to iterators</a></li><li class="chapter-item expanded "><a href="../3.MovingToModernCpp/item14.html">Item 14: Declare functions noexcept if they won't emit exceptions</a></li><li class="chapter-item expanded "><a href="../3.MovingToModernCpp/item15.html">Item 15: Use constexpr whenever possible</a></li><li class="chapter-item expanded "><a href="../3.MovingToModernCpp/item16.html">Item 16: Make const member functions thread safe</a></li><li class="chapter-item expanded "><a href="../3.MovingToModernCpp/item17.html">Item 17: Understand special member funciton generation</a></li></ol></li><li class="chapter-item expanded "><div>Chapter 4. Smart Pointer</div></li><li><ol class="section"><li class="chapter-item expanded "><a href="../4.SmartPointers/item18.html">Item 18: Use std::unique_ptr for exclusive-ownership resource management</a></li><li class="chapter-item expanded "><a href="../4.SmartPointers/item19.html">Item 19: Use std::shared_ptr for shared-ownership resource management</a></li><li class="chapter-item expanded "><a href="../4.SmartPointers/item20.html">Item 20: Use std::weak_ptr for std::shared_ptr like pointers that can dangle</a></li><li class="chapter-item expanded "><a href="../4.SmartPointers/item21.html">Item 21: Prefer std::make_unique and std::make_shared to direct use of new</a></li><li class="chapter-item expanded "><a href="../4.SmartPointers/item22.html">Item 22: When using the Pimpl Idiom, define special member functions in the implementation file</a></li></ol></li><li class="chapter-item expanded "><div>Chapter 5. Rvalue References, Move Semantics, and Perfect Forwarding</div></li><li><ol class="section"><li class="chapter-item expanded "><a href="../5.RRefMovSemPerfForw/item23.html">Item 23: Understand std::move and std::forward</a></li><li class="chapter-item expanded "><a href="../5.RRefMovSemPerfForw/item24.html">Item 24: Distinguish universal references from rvalue references</a></li><li class="chapter-item expanded "><a href="../5.RRefMovSemPerfForw/item25.html">Item 25: Use std::move on rvalue references, std::forward on universal references</a></li><li class="chapter-item expanded "><a href="../5.RRefMovSemPerfForw/item26.html">Item 26: Avoid overloading on universal references</a></li><li class="chapter-item expanded "><a href="../5.RRefMovSemPerfForw/item27.html">Item 27: Familiarize yourself with alternatives to overaloading on univeral references</a></li><li class="chapter-item expanded "><a href="../5.RRefMovSemPerfForw/item28.html">Item 28: Understand reference collapsing</a></li><li class="chapter-item expanded "><a href="../5.RRefMovSemPerfForw/item29.html">Item 29: Assume that move operations are not present not cheap, and not used</a></li><li class="chapter-item expanded "><a href="../5.RRefMovSemPerfForw/item30.html">Item 30: Familiarize yourself with perfect forwarding failure cases</a></li></ol></li><li class="chapter-item expanded "><div>Chapter 6. Lambda Expressions</div></li><li><ol class="section"><li class="chapter-item expanded "><a href="../6.LambdaExpressions/item31.html">Item 31: Avoid default capture modes</a></li><li class="chapter-item expanded "><a href="../6.LambdaExpressions/item32.html">Item 32: Use init capture to move objects into closures</a></li><li class="chapter-item expanded "><a href="../6.LambdaExpressions/item33.html">Item 33: Use decltype on auto&&parameters to std::forward them</a></li><li class="chapter-item expanded "><a href="../6.LambdaExpressions/item34.html" class="active">Item 34: Prefer lambdas to std::bind</a></li></ol></li><li class="chapter-item expanded "><div>Chapter 7. The Concurrency API</div></li><li><ol class="section"><li class="chapter-item expanded "><a href="../7.TheConcurrencyAPI/Item35.html">Item 35: Prefer task-based programming to thread-based</a></li><li class="chapter-item expanded "><a href="../7.TheConcurrencyAPI/item36.html">Item 36: Specify std::launch::async if asynchronicity is essential</a></li><li class="chapter-item expanded "><a href="../7.TheConcurrencyAPI/item37.html">Item 37: Make std::threads unjionable on all paths</a></li><li class="chapter-item expanded "><a href="../7.TheConcurrencyAPI/item38.html">Item 38: Be aware of varying thread handle destructor behavior</a></li><li class="chapter-item expanded "><a href="../7.TheConcurrencyAPI/item39.html">Item 39: Consider void futures for one-shot event communication</a></li><li class="chapter-item expanded "><a href="../7.TheConcurrencyAPI/item40.html">Item 40: Use std::atomic for concurrency, volatile for special memory</a></li></ol></li><li class="chapter-item expanded "><div>Chapter 8. Tweaks</div></li><li><ol class="section"><li class="chapter-item expanded "><a href="../8.Tweaks/item41.html">Item 41: Consider pass by value for copyable parameters that are cheap to move and always copied</a></li><li class="chapter-item expanded "><a href="../8.Tweaks/item42.html">Item 42: Consider emplacement instead of insertion</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>
</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="条款三十四考虑lambda而非stdbind"><a class="header" href="#条款三十四考虑lambda而非stdbind">条款三十四:考虑<em>lambda</em>而非<code>std::bind</code></a></h2>
<p><strong>Item 34: Prefer lambdas to <code>std::bind</code></strong></p>
<p>C++11中的<code>std::bind</code>是C++98的<code>std::bind1st</code><code>std::bind2nd</code>的后续但在2005年已经非正式成为了标准库的一部分。那时标准化委员采用了TR1的文档其中包含了<code>bind</code>的规范。在TR1中<code>bind</code>位于不同的命名空间,因此它是<code>std::tr1::bind</code>,而不是<code>std::bind</code>,接口细节也有所不同)。这段历史意味着一些程序员有十年及以上的<code>std::bind</code>使用经验。如果你是其中之一可能会不愿意放弃一个对你有用的工具。这是可以理解的但是在这种情况下改变是更好的因为在C++11中<em>lambda</em>几乎总是比<code>std::bind</code>更好的选择。 从C++14开始<em>lambda</em>的作用不仅强大,而且是完全值得使用的。</p>
<p>这个条款假设你熟悉<code>std::bind</code>。 如果不是这样,你将需要获得基本的了解,然后再继续。 无论如何,这样的理解都是值得的,因为你永远不知道何时会在阅读或维护的代码库中遇到<code>std::bind</code></p>
<p><a href="https://github.com/kelthuzadx/EffectiveModernCppChinese/blob/master/6.LambdaExpressions/item32.md">Item32</a>中一样,我们将从<code>std::bind</code>返回的函数对象称为<strong>bind对象</strong><em>bind objects</em>)。</p>
<p>优先<em>lambda</em>而不是<code>std::bind</code>的最重要原因是<em>lambda</em>更易读。 例如,假设我们有一个设置警报器的函数:</p>
<pre><code class="language-c++">//一个时间点的类型定义语法见条款9
using Time = std::chrono::steady_clock::time_point;
//“enum class”见条款10
enum class Sound { Beep, Siren, Whistle };
//时间段的类型定义
using Duration = std::chrono::steady_clock::duration;
//在时间t使用s声音响铃时长d
void setAlarm(Time t, Sound s, Duration d);
</code></pre>
<p>进一步假设在程序的某个时刻我们已经确定需要设置一个小时后响30秒的警报器。 但是,具体声音仍未确定。我们可以编写一个<em>lambda</em>来修改<code>setAlarm</code>的界面,以便仅需要指定声音:</p>
<pre><code class="language-c++">//setSoundL“L”指代“lambda”是个函数对象允许指定一小时后响30秒的警报器的声音
auto setSoundL =
[](Sound s)
{
//使std::chrono部件在不指定限定的情况下可用
using namespace std::chrono;
setAlarm(steady_clock::now() + hours(1), //一小时后响30秒的闹钟
s, //译注setAlarm三行高亮
seconds(30));
};
</code></pre>
<p>我们在<em>lambda</em>中高亮了对<code>setAlarm</code>的调用。这看来起是一个很正常的函数调用,即使是几乎没有<em>lambda</em>经验的读者也可以看到:传递给<em>lambda</em>的形参<code>s</code>又作为实参被传递给了<code>setAlarm</code></p>
<p>我们通过使用标准后缀如秒(<code>s</code>),毫秒(<code>ms</code>)和小时(<code>h</code>等简化在C++14中的代码其中标准后缀基于C++11对用户自定义常量的支持。这些后缀在<code>std::literals</code>命名空间中实现,因此上述代码可以按照以下方式重写:</p>
<pre><code class="language-c++">auto setSoundL =
[](Sound s)
{
using namespace std::chrono;
using namespace std::literals; //对于C++14后缀
setAlarm(steady_clock::now() + 1h, //C++14写法但是含义同上
s,
30s);
};
</code></pre>
<p>下面是我们第一次编写对应的<code>std::bind</code>调用。这里存在一个我们后续会修复的错误,但正确的代码会更加复杂,即使是此简化版本也会凸显一些重要问题:</p>
<pre><code class="language-c++">using namespace std::chrono; //同上
using namespace std::literals;
using namespace std::placeholders; //“_1”使用需要
auto setSoundB = //“B”代表“bind”
std::bind(setAlarm,
steady_clock::now() + 1h, //不正确!见下
_1,
30s);
</code></pre>
<p>我想像在之前的<em>lambda</em>中一样高亮对<code>setAlarm</code>的调用,但是没这么个调用让我高亮。这段代码的读者只需知道,调用<code>setSoundB</code>会使用在对<code>std::bind</code>的调用中所指定的时间和持续时间来调用<code>setAlarm</code>。对于门外汉来说,占位符“<code>_1</code>”完全是一个魔法,但即使是知情的读者也必须从思维上将占位符中的数字映射到其在<code>std::bind</code>形参列表中的位置,以便明白调用<code>setSoundB</code>时的第一个实参会被传递进<code>setAlarm</code>,作为调用<code>setAlarm</code>的第二个实参。在对<code>std::bind</code>的调用中未标识此实参的类型,因此读者必须查阅<code>setAlarm</code>声明以确定将哪种实参传递给<code>setSoundB</code></p>
<p>但正如我所说,代码并不完全正确。在<em>lambda</em>中,表达式<code>steady_clock::now() + 1h</code>显然是<code>setAlarm</code>的实参。调用<code>setAlarm</code>时将对其进行计算。可以理解:我们希望在调用<code>setAlarm</code>后一小时响铃。但是,在<code>std::bind</code>调用中,将<code>steady_clock::now() + 1h</code>作为实参传递给了<code>std::bind</code>,而不是<code>setAlarm</code>。这意味着将在调用<code>std::bind</code>时对表达式进行求值并且该表达式产生的时间将存储在产生的bind对象中。结果警报器将被设置为在<strong>调用<code>std::bind</code>后一小时</strong>发出声音,而不是在调用<code>setAlarm</code>一小时后发出。</p>
<p>要解决此问题,需要告诉<code>std::bind</code>推迟对表达式的求值,直到调用<code>setAlarm</code>为止,而这样做的方法是将对<code>std::bind</code>的第二个调用嵌套在第一个调用中:</p>
<pre><code class="language-c++">auto setSoundB =
std::bind(setAlarm,
std::bind(std::plus&lt;&gt;(), std::bind(steady_clock::now), 1h),
_1,
30s);
</code></pre>
<p>如果你熟悉C++98的<code>std::plus</code>模板,你可能会惊讶地发现在此代码中,尖括号之间未指定任何类型,即该代码包含“<code>std::plus&lt;&gt;</code>”,而不是“<code>std::plus&lt;type&gt;</code>”。 在C++14中通常可以省略标准运算符模板的模板类型实参因此无需在此处提供。 C++11没有提供此类功能因此等效于<em>lambda</em>的C++11 <code>std::bind</code>为:</p>
<pre><code class="language-c++">using namespace std::chrono; //同上
using namespace std::placeholders;
auto setSoundB =
std::bind(setAlarm,
std::bind(std::plus&lt;steady_clock::time_point&gt;(),
std::bind(steady_clock::now),
hours(1)),
_1,
seconds(30));
</code></pre>
<p>如果此时<em>lambda</em>看起来还没有吸引力,那么应该检查一下视力了。</p>
<p><code>setAlarm</code>重载时,会出现一个新问题。 假设有一个重载函数,其中第四个形参指定了音量:</p>
<pre><code class="language-c++">enum class Volume { Normal, Loud, LoudPlusPlus };
void setAlarm(Time t, Sound s, Duration d, Volume v);
</code></pre>
<p><em>lambda</em>能继续像以前一样使用,因为根据重载规则选择了<code>setAlarm</code>的三实参版本:</p>
<pre><code class="language-c++">auto setSoundL = //和之前一样
[](Sound s)
{
using namespace std::chrono;
setAlarm(steady_clock::now() + 1h, //可以调用三实参版本的setAlarm
s,
30s);
};
</code></pre>
<p>然而,<code>std::bind</code>的调用将会编译失败:</p>
<pre><code class="language-c++">auto setSoundB = //错误哪个setAlarm
std::bind(setAlarm,
std::bind(std::plus&lt;&gt;(),
steady_clock::now(),
1h),
_1,
30s);
</code></pre>
<p>这里的问题是,编译器无法确定应将两个<code>setAlarm</code>函数中的哪一个传递给<code>std::bind</code>。 它们仅有的是一个函数名称,而这个单一个函数名称是有歧义的。</p>
<p>要使对<code>std::bind</code>的调用能编译,必须将<code>setAlarm</code>强制转换为适当的函数指针类型:</p>
<pre><code class="language-c++">using SetAlarm3ParamType = void(*)(Time t, Sound s, Duration d);
auto setSoundB = //现在可以了
std::bind(static_cast&lt;SetAlarm3ParamType&gt;(setAlarm),
std::bind(std::plus&lt;&gt;(),
steady_clock::now(),
1h),
_1,
30s);
</code></pre>
<p>但这在<em>lambda</em><code>std::bind</code>的使用上带来了另一个区别。 在<code>setSoundL</code>的函数调用操作符(即<em>lambda</em>的闭包类对应的函数调用操作符)内部,对<code>setAlarm</code>的调用是正常的函数调用,编译器可以按常规方式进行内联:</p>
<pre><code class="language-c++">setSoundL(Sound::Siren); //setAlarm函数体在这可以很好地内联
</code></pre>
<p>但是,对<code>std::bind</code>的调用是将函数指针传递给<code>setAlarm</code>,这意味着在<code>setSoundB</code>的函数调用操作符(即绑定对象的函数调用操作符)内部,对<code>setAlarm</code>的调用是通过一个函数指针。 编译器不太可能通过函数指针内联函数,这意味着与通过<code>setSoundL</code>进行调用相比,通过<code>setSoundB</code><code>setAlarm的</code>调用,其函数不大可能被内联:</p>
<pre><code class="language-c++">setSoundB(Sound::Siren); //setAlarm函数体在这不太可能内联
</code></pre>
<p>因此,使用<em>lambda</em>可能会比使用<code>std::bind</code>能生成更快的代码。</p>
<p><code>setAlarm</code>示例仅涉及一个简单的函数调用。如果你想做更复杂的事情,使用<em>lambda</em>会更有利。 例如考虑以下C++14的<em>lambda</em>使用,它返回其实参是否在最小值(<code>lowVal</code>)和最大值(<code>highVal</code>)之间的结果,其中<code>lowVal</code><code>highVal</code>是局部变量:</p>
<pre><code class="language-c++">auto betweenL =
[lowVal, highVal]
(const auto&amp; val) //C++14
{ return lowVal &lt;= val &amp;&amp; val &lt;= highVal; };
</code></pre>
<p>使用<code>std::bind</code>可以表达相同的内容,但是该构造是一个通过晦涩难懂的代码来保证工作安全性的示例:</p>
<pre><code class="language-c++">using namespace std::placeholders; //同上
auto betweenB =
std::bind(std::logical_and&lt;&gt;(), //C++14
std::bind(std::less_equal&lt;&gt;(), lowVal, _1),
std::bind(std::less_equal&lt;&gt;(), _1, highVal));
</code></pre>
<p>在C++11中我们必须指定要比较的类型然后<code>std::bind</code>调用将如下所示:</p>
<pre><code class="language-c++">auto betweenB =
std::bind(std::logical_and&lt;bool&gt;(), //C++11版本
std::bind(std::less_equal&lt;int&gt;(), lowVal, _1),
std::bind(std::less_equal&lt;int&gt;(), _1, highVal));
</code></pre>
<p>当然在C++11中<em>lambda</em>也不能采用<code>auto</code>形参,因此它也必须指定一个类型:</p>
<pre><code class="language-c++">auto betweenL = //C++11版本
[lowVal, highVal]
(int val)
{ return lowVal &lt;= val &amp;&amp; val &lt;= highVal; };
</code></pre>
<p>无论哪种方式,我希望我们都能同意,<em>lambda</em>版本不仅更短,而且更易于理解和维护。</p>
<p>之前我就说过,对于那些没有<code>std::bind</code>使用经验的人,其占位符(例如<code>_1</code><code>_2</code>等)都是魔法。 但是这不仅仅在于占位符的行为是不透明的。 假设我们有一个函数可以创建<code>Widget</code>的压缩副本,</p>
<pre><code class="language-c++">enum class CompLevel { Low, Normal, High }; //压缩等级
Widget compress(const Widget&amp; w, //制作w的压缩副本
CompLevel lev);
</code></pre>
<p>并且我们想创建一个函数对象,该函数对象允许我们指定<code>Widget w</code>的压缩级别。这种使用<code>std::bind</code>的话将创建一个这样的对象:</p>
<pre><code class="language-c++">Widget w;
using namespace std::placeholders;
auto compressRateB = std::bind(compress, w, _1);
</code></pre>
<p>现在,当我们将<code>w</code>传递给<code>std::bind</code>时,必须将其存储起来,以便以后进行压缩。它存储在对象<code>compressRateB</code>中,但是它是如何被存储的呢——是通过值还是引用?之所以会有所不同,是因为如果在对<code>std::bind</code>的调用与对<code>compressRateB</code>的调用之间修改了<code>w</code>,则按引用捕获的<code>w</code>将反映这个更改,而按值捕获则不会。</p>
<p>答案是它是按值捕获的(<code>std::bind</code>总是拷贝它的实参,但是调用者可以使用引用来存储实参,这要通过应用<code>std::ref</code>到实参上实现。<code>auto compressRateB = std::bind(compress, std::ref(w), _1);</code>的结果就是<code>compressRateB</code>行为像是持有<code>w</code>的引用而非副本。),但唯一知道的方法是记住<code>std::bind</code>的工作方式;在对<code>std::bind</code>的调用中没有任何迹象。然而在<em>lambda</em>方法中,其中<code>w</code>是通过值还是通过引用捕获是显式的:</p>
<pre><code class="language-c++">auto compressRateL = //w是按值捕获lev是按值传递
[w](CompLevel lev)
{ return compress(w, lev); };
</code></pre>
<p>同样明确的是形参是如何传递给<em>lambda</em>的。 在这里,很明显形参<code>lev</code>是通过值传递的。 因此:</p>
<pre><code class="language-c++">compressRateL(CompLevel::High); //实参按值传递
</code></pre>
<p>但是在对由<code>std::bind</code>生成的对象调用中,实参如何传递?</p>
<pre><code class="language-c++">compressRateB(CompLevel::High); //实参如何传递?
</code></pre>
<p>同样,唯一的方法是记住<code>std::bind</code>的工作方式。答案是传递给bind对象的所有实参都是通过引用传递的因为此类对象的函数调用运算符使用完美转发。</p>
<p><em>lambda</em>相比,使用<code>std::bind</code>进行编码的代码可读性较低,表达能力较低,并且效率可能较低。 在C++14中没有<code>std::bind</code>的合理用例。 但是在C++11中可以在两个受约束的情况下证明使用<code>std::bind</code>是合理的:</p>
<ul>
<li><strong>移动捕获</strong>。C++11的<em>lambda</em>不提供移动捕获,但是可以通过结合<em>lambda</em><code>std::bind</code>来模拟。 有关详细信息,请参阅<a href="https://github.com/kelthuzadx/EffectiveModernCppChinese/blob/master/6.LambdaExpressions/item32.md">Item32</a>该条款还解释了在C++14中<em>lambda</em>对初始化捕获的支持消除了这个模拟的需求。</li>
<li><strong>多态函数对象</strong>。因为bind对象上的函数调用运算符使用完美转发所以它可以接受任何类型的实参<a href="https://github.com/kelthuzadx/EffectiveModernCppChinese/blob/master/5.RRefMovSemPerfForw/item30.md">Item30</a>中描述的完美转发的限制为界限)。当你要绑定带有模板化函数调用运算符的对象时,此功能很有用。 例如这个类,</li>
</ul>
<pre><code class="language-c++">class PolyWidget {
public:
template&lt;typename T&gt;
void operator()(const T&amp; param);
};
</code></pre>
<p><code>std::bind</code>可以如下绑定一个<code>PolyWidget</code>对象:</p>
<pre><code class="language-c++">PolyWidget pw;
auto boundPW = std::bind(pw, _1);
</code></pre>
<p><code>boundPW</code>可以接受任意类型的对象了:</p>
<pre><code class="language-c++">boundPW(1930); //传int给PolyWidget::operator()
boundPW(nullptr); //传nullptr给PolyWidget::operator()
boundPW(&quot;Rosebud&quot;); //传字面值给PolyWidget::operator()
</code></pre>
<p>这一点无法使用C++11的<em>lambda</em>做到。 但是在C++14中可以通过带有<code>auto</code>形参的<em>lambda</em>轻松实现:</p>
<pre><code class="language-c++">auto boundPW = [pw](const auto&amp; param) //C++14
{ pw(param); };
</code></pre>
<p>当然这些是特殊情况并且是暂时的特殊情况因为支持C++14 <em>lambda</em>的编译器越来越普遍了。</p>
<p><code>bind</code>在2005年被非正式地添加到C++中时与1998年的前身相比有了很大的改进。 在C++11中增加了<em>lambda</em>支持,这使得<code>std::bind</code>几乎已经过时了从C++14开始更是没有很好的用例了。</p>
<p><strong>请记住:</strong></p>
<ul>
<li>与使用<code>std::bind</code>相比,<em>lambda</em>更易读,更具表达力并且可能更高效。</li>
<li>只有在C++11中<code>std::bind</code>可能对实现移动捕获或绑定带有模板化函数调用运算符的对象时会很有用。</li>
</ul>
</main>
<nav class="nav-wrapper" aria-label="Page navigation">
<!-- Mobile navigation buttons -->
<a rel="prev" href="../6.LambdaExpressions/item33.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="../7.TheConcurrencyAPI/Item35.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/item33.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="../7.TheConcurrencyAPI/Item35.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>