mirror of
https://github.com/CnTransGroup/EffectiveModernCppChinese.git
synced 2025-01-06 02:10:19 +08:00
383 lines
34 KiB
HTML
383 lines
34 KiB
HTML
<!DOCTYPE HTML>
|
||
<html lang="zh" class="sidebar-visible no-js light">
|
||
<head>
|
||
<!-- Book generated using mdBook -->
|
||
<meta charset="UTF-8">
|
||
<title>Item 34:优先考虑lambda表达式而非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">简介</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">Item 33:对于std::forward的auto&&形参使用decltype</a></li><li class="chapter-item expanded "><a href="../6.LambdaExpressions/item34.html" class="active">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::atomic,volatile用于特殊内存区</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="条款三十四考虑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="../6.LambdaExpressions/item32.html">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<>(), std::bind(steady_clock::now), 1h),
|
||
_1,
|
||
30s);
|
||
</code></pre>
|
||
<p>如果你熟悉C++98的<code>std::plus</code>模板,你可能会惊讶地发现在此代码中,尖括号之间未指定任何类型,即该代码包含“<code>std::plus<></code>”,而不是“<code>std::plus<type></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<steady_clock::time_point>(),
|
||
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<>(),
|
||
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<SetAlarm3ParamType>(setAlarm),
|
||
std::bind(std::plus<>(),
|
||
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& val) //C++14
|
||
{ return lowVal <= val && val <= 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<>(), //C++14
|
||
std::bind(std::less_equal<>(), lowVal, _1),
|
||
std::bind(std::less_equal<>(), _1, highVal));
|
||
</code></pre>
|
||
<p>在C++11中,我们必须指定要比较的类型,然后<code>std::bind</code>调用将如下所示:</p>
|
||
<pre><code class="language-c++">auto betweenB =
|
||
std::bind(std::logical_and<bool>(), //C++11版本
|
||
std::bind(std::less_equal<int>(), lowVal, _1),
|
||
std::bind(std::less_equal<int>(), _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 <= val && val <= 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& 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="../6.LambdaExpressions/item32.html">Item32</a>,该条款还解释了在C++14中,<em>lambda</em>对初始化捕获的支持消除了这个模拟的需求。</li>
|
||
<li><strong>多态函数对象</strong>。因为bind对象上的函数调用运算符使用完美转发,所以它可以接受任何类型的实参(以<a href="../5.RRefMovSemPerfForw/item30.html">Item30</a>中描述的完美转发的限制为界限)。当你要绑定带有模板化函数调用运算符的对象时,此功能很有用。 例如这个类,</li>
|
||
</ul>
|
||
<pre><code class="language-c++">class PolyWidget {
|
||
public:
|
||
template<typename T>
|
||
void operator()(const T& 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("Rosebud"); //传字面值给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& 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>
|