mirror of
https://github.com/CnTransGroup/EffectiveModernCppChinese.git
synced 2025-01-06 02:10:19 +08:00
324 lines
34 KiB
HTML
324 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 23:理解std::move和std::forward - 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" class="active">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">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>
|
||
<h1 id="第5章-右值引用移动语义完美转发"><a class="header" href="#第5章-右值引用移动语义完美转发">第5章 右值引用,移动语义,完美转发</a></h1>
|
||
<p><strong>CHAPTER 5 RValue References, Move Semantics and Perfect Forwarding</strong></p>
|
||
<p>当你第一次了解到移动语义(<em>move semantics</em>)和完美转发(<em>perfect forwarding</em>)的时候,它们看起来非常直观:</p>
|
||
<ul>
|
||
<li>
|
||
<p><strong>移动语义</strong>使编译器有可能用廉价的移动操作来代替昂贵的拷贝操作。正如拷贝构造函数和拷贝赋值操作符给了你控制拷贝语义的权力,移动构造函数和移动赋值操作符也给了你控制移动语义的权力。移动语义也允许创建只可移动(<em>move-only</em>)的类型,例如<code>std::unique_ptr</code>,<code>std::future</code>和<code>std::thread</code>。</p>
|
||
</li>
|
||
<li>
|
||
<p><strong>完美转发</strong>使接收任意数量实参的函数模板成为可能,它可以将实参转发到其他的函数,使目标函数接收到的实参与被传递给转发函数的实参保持一致。</p>
|
||
</li>
|
||
</ul>
|
||
<p><strong>右值引用</strong>是连接这两个截然不同的概念的胶合剂。它是使移动语义和完美转发变得可能的基础语言机制。</p>
|
||
<p>你对这些特点越熟悉,你就越会发现,你的初印象只不过是冰山一角。移动语义、完美转发和右值引用的世界比它所呈现的更加微妙。举个例子,<code>std::move</code>并不移动任何东西,完美转发也并不完美。移动操作并不永远比复制操作更廉价;即便如此,它也并不总是像你期望的那么廉价。而且,它也并不总是被调用,即使在当移动操作可用的时候。构造“<code>type&&</code>”也并非总是代表一个右值引用。</p>
|
||
<p>无论你挖掘这些特性有多深,它们看起来总是还有更多隐藏起来的部分。幸运的是,它们的深度总是有限的。本章将会带你到最基础的部分。一旦到达,C++11的这部分特性将会具有非常大的意义。比如,你会掌握<code>std::move</code>和<code>std::forward</code>的惯用法。你能够适应“<code>type&&</code>”的歧义性质。你会理解移动操作的令人惊奇的不同表现的背后真相。这些片段都会豁然开朗。在这一点上,你会重新回到一开始的状态,因为移动语义、完美转发和右值引用都会又一次显得直截了当。但是这一次,它们不再使人困惑。</p>
|
||
<p>在本章的这些小节中,非常重要的一点是要牢记形参永远是<strong>左值</strong>,即使它的类型是一个右值引用。比如,假设</p>
|
||
<pre><code class="language-c++">void f(Widget&& w);
|
||
</code></pre>
|
||
<p>形参<code>w</code>是一个左值,即使它的类型是一个rvalue-reference-to-<code>Widget</code>。(如果这里震惊到你了,请重新回顾从本书<a href="https://github.com/CnTransGroup/EffectiveModernCppChinese/blob/master/src/Introduction.md">简介</a>开始的关于左值和右值的总览。)</p>
|
||
<h2 id="条款二十三理解stdmove和stdforward"><a class="header" href="#条款二十三理解stdmove和stdforward">条款二十三:理解<code>std::move</code>和<code>std::forward</code></a></h2>
|
||
<p><strong>Item 23: Understand <code>std::move</code> and <code>std::forward</code></strong></p>
|
||
<p>为了了解<code>std::move</code>和<code>std::forward</code>,一种有用的方式是从<strong>它们不做什么</strong>这个角度来了解它们。<code>std::move</code>不移动(move)任何东西,<code>std::forward</code>也不转发(forward)任何东西。在运行时,它们不做任何事情。它们不产生任何可执行代码,一字节也没有。</p>
|
||
<p><code>std::move</code>和<code>std::forward</code>仅仅是执行转换(cast)的函数(事实上是函数模板)。<code>std::move</code>无条件的将它的实参转换为右值,而<code>std::forward</code>只在特定情况满足时下进行转换。它们就是如此。这样的解释带来了一些新的问题,但是从根本上而言,这就是全部内容。</p>
|
||
<p>为了使这个故事更加的具体,这里是一个C++11的<code>std::move</code>的示例实现。它并不完全满足标准细则,但是它已经非常接近了。</p>
|
||
<pre><code class="language-cpp">template<typename T> //在std命名空间
|
||
typename remove_reference<T>::type&&
|
||
move(T&& param)
|
||
{
|
||
using ReturnType = //别名声明,见条款9
|
||
typename remove_reference<T>::type&&;
|
||
|
||
return static_cast<ReturnType>(param);
|
||
}
|
||
</code></pre>
|
||
<p>我为你们高亮了这段代码的两部分(译者注:高亮的部分为函数名<code>move</code>和<code>static_cast<ReturnType>(param)</code>)。一个是函数名字,因为函数的返回值非常具有干扰性,而且我不想你们被它搞得晕头转向。另外一个高亮的部分是包含这段函数的本质的转换。正如你所见,<code>std::move</code>接受一个对象的引用(准确的说,一个通用引用(universal reference),见<a href="../5.RRefMovSemPerfForw/item24.html">Item24</a>),返回一个指向同对象的引用。</p>
|
||
<p>该函数返回类型的<code>&&</code>部分表明<code>std::move</code>函数返回的是一个右值引用,但是,正如<a href="../5.RRefMovSemPerfForw/item28.html">Item28</a>所解释的那样,如果类型<code>T</code>恰好是一个左值引用,那么<code>T&&</code>将会成为一个左值引用。为了避免如此,<em>type trait</em>(见<a href="../3.MovingToModernCpp/item9.html">Item9</a>)<code>std::remove_reference</code>应用到了类型<code>T</code>上,因此确保了<code>&&</code>被正确的应用到了一个不是引用的类型上。这保证了<code>std::move</code>返回的真的是右值引用,这很重要,因为函数返回的右值引用是右值。因此,<code>std::move</code>将它的实参转换为一个右值,这就是它的全部作用。</p>
|
||
<p>此外,<code>std::move</code>在C++14中可以被更简单地实现。多亏了函数返回值类型推导(见<a href="../1.DeducingTypes/item3.html">Item3</a>)和标准库的模板别名<code>std::remove_reference_t</code>(见<a href="../3.MovingToModernCpp/item9.html">Item9</a>),<code>std::move</code>可以这样写:</p>
|
||
<pre><code class="language-cpp">template<typename T>
|
||
decltype(auto) move(T&& param) //C++14,仍然在std命名空间
|
||
{
|
||
using ReturnType = remove_referece_t<T>&&;
|
||
return static_cast<ReturnType>(param);
|
||
}
|
||
</code></pre>
|
||
<p>看起来更简单,不是吗?</p>
|
||
<p>因为<code>std::move</code>除了转换它的实参到右值以外什么也不做,有一些提议说它的名字叫<code>rvalue_cast</code>之类可能会更好。虽然可能确实是这样,但是它的名字已经是<code>std::move</code>,所以记住<code>std::move</code>做什么和不做什么很重要。它只进行转换,不移动任何东西。</p>
|
||
<p>当然,右值本来就是移动操作的候选者,所以对一个对象使用<code>std::move</code>就是告诉编译器,这个对象很适合被移动。所以这就是为什么<code>std::move</code>叫现在的名字:更容易指定可以被移动的对象。</p>
|
||
<p>事实上,右值只不过<strong>经常</strong>是移动操作的候选者。假设你有一个类,它用来表示一段注解。这个类的构造函数接受一个包含有注解的<code>std::string</code>作为形参,然后它复制该形参到数据成员。假设你了解<a href="../8.Tweaks/item41.html">Item41</a>,你声明一个值传递的形参:</p>
|
||
<pre><code class="language-cpp">class Annotation {
|
||
public:
|
||
explicit Annotation(std::string text); //将会被复制的形参,
|
||
… //如同条款41所说,
|
||
}; //值传递
|
||
</code></pre>
|
||
<p>但是<code>Annotation</code>类的构造函数仅仅是需要读取<code>text</code>的值,它并不需要修改它。为了和历史悠久的传统:能使用<code>const</code>就使用<code>const</code>保持一致,你修订了你的声明以使<code>text</code>变成<code>const</code>:</p>
|
||
<pre><code class="language-cpp">class Annotation {
|
||
public:
|
||
explicit Annotation(const std::string text);
|
||
…
|
||
};
|
||
</code></pre>
|
||
<p>当复制<code>text</code>到一个数据成员的时候,为了避免一次复制操作的代价,你仍然记得来自<a href="../8.Tweaks/item41.html">Item41</a>的建议,把<code>std::move</code>应用到<code>text</code>上,因此产生一个右值:</p>
|
||
<pre><code class="language-cpp">class Annotation {
|
||
public:
|
||
explicit Annotation(const std::string text)
|
||
:value(std::move(text)) //“移动”text到value里;这段代码执行起来
|
||
{ … } //并不是看起来那样
|
||
|
||
…
|
||
|
||
private:
|
||
std::string value;
|
||
};
|
||
</code></pre>
|
||
<p>这段代码可以编译,可以链接,可以运行。这段代码将数据成员<code>value</code>设置为<code>text</code>的值。这段代码与你期望中的完美实现的唯一区别,是<code>text</code>并不是被移动到<code>value</code>,而是被<strong>拷贝</strong>。诚然,<code>text</code>通过<code>std::move</code>被转换到右值,但是<code>text</code>被声明为<code>const std::string</code>,所以在转换之前,<code>text</code>是一个左值的<code>const std::string</code>,而转换的结果是一个右值的<code>const std::string</code>,但是纵观全程,<code>const</code>属性一直保留。</p>
|
||
<p>当编译器决定哪一个<code>std::string</code>的构造函数被调用时,考虑它的作用,将会有两种可能性:</p>
|
||
<pre><code class="language-cpp">class string { //std::string事实上是
|
||
public: //std::basic_string<char>的类型别名
|
||
…
|
||
string(const string& rhs); //拷贝构造函数
|
||
string(string&& rhs); //移动构造函数
|
||
…
|
||
};
|
||
</code></pre>
|
||
<p>在类<code>Annotation</code>的构造函数的成员初始化列表中,<code>std::move(text)</code>的结果是一个<code>const std::string</code>的右值。这个右值不能被传递给<code>std::string</code>的移动构造函数,因为移动构造函数只接受一个指向<strong>non-<code>const</code><strong>的<code>std::string</code>的右值引用。然而,该右值却可以被传递给<code>std::string</code>的拷贝构造函数,因为lvalue-reference-to-<code>const</code>允许被绑定到一个<code>const</code>右值上。因此,<code>std::string</code>在成员初始化的过程中调用了</strong>拷贝</strong>构造函数,即使<code>text</code>已经被转换成了右值。这样是为了确保维持<code>const</code>属性的正确性。从一个对象中移动出某个值通常代表着修改该对象,所以语言不允许<code>const</code>对象被传递给可以修改他们的函数(例如移动构造函数)。</p>
|
||
<p>从这个例子中,可以总结出两点。第一,不要在你希望能移动对象的时候,声明他们为<code>const</code>。对<code>const</code>对象的移动请求会悄无声息的被转化为拷贝操作。第二点,<code>std::move</code>不仅不移动任何东西,而且它也不保证它执行转换的对象可以被移动。关于<code>std::move</code>,你能确保的唯一一件事就是将它应用到一个对象上,你能够得到一个右值。</p>
|
||
<p>关于<code>std::forward</code>的故事与<code>std::move</code>是相似的,但是与<code>std::move</code>总是<strong>无条件</strong>的将它的实参为右值不同,<code>std::forward</code>只有在满足一定条件的情况下才执行转换。<code>std::forward</code>是<strong>有条件</strong>的转换。要明白什么时候它执行转换,什么时候不,想想<code>std::forward</code>的典型用法。最常见的情景是一个模板函数,接收一个通用引用形参,并将它传递给另外的函数:</p>
|
||
<pre><code class="language-cpp">void process(const Widget& lvalArg); //处理左值
|
||
void process(Widget&& rvalArg); //处理右值
|
||
|
||
template<typename T> //用以转发param到process的模板
|
||
void logAndProcess(T&& param)
|
||
{
|
||
auto now = //获取现在时间
|
||
std::chrono::system_clock::now();
|
||
|
||
makeLogEntry("Calling 'process'", now);
|
||
process(std::forward<T>(param));
|
||
}
|
||
</code></pre>
|
||
<p>考虑两次对<code>logAndProcess</code>的调用,一次左值为实参,一次右值为实参:</p>
|
||
<pre><code class="language-cpp">Widget w;
|
||
|
||
logAndProcess(w); //用左值调用
|
||
logAndProcess(std::move(w)); //用右值调用
|
||
</code></pre>
|
||
<p>在<code>logAndProcess</code>函数的内部,形参<code>param</code>被传递给函数<code>process</code>。函数<code>process</code>分别对左值和右值做了重载。当我们使用左值来调用<code>logAndProcess</code>时,自然我们期望该左值被当作左值转发给<code>process</code>函数,而当我们使用右值来调用<code>logAndProcess</code>函数时,我们期望<code>process</code>函数的右值重载版本被调用。</p>
|
||
<p>但是<code>param</code>,正如所有的其他函数形参一样,是一个左值。每次在函数<code>logAndProcess</code>内部对函数<code>process</code>的调用,都会因此调用函数<code>process</code>的左值重载版本。为防如此,我们需要一种机制:当且仅当传递给函数<code>logAndProcess</code>的用以初始化<code>param</code>的实参是一个右值时,<code>param</code>会被转换为一个右值。这就是<code>std::forward</code>做的事情。这就是为什么<code>std::forward</code>是一个<strong>有条件</strong>的转换:它的实参用右值初始化时,转换为一个右值。</p>
|
||
<p>你也许会想知道<code>std::forward</code>是怎么知道它的实参是否是被一个右值初始化的。举个例子,在上述代码中,<code>std::forward</code>是怎么分辨<code>param</code>是被一个左值还是右值初始化的? 简短的说,该信息藏在函数<code>logAndProcess</code>的模板参数<code>T</code>中。该参数被传递给了函数<code>std::forward</code>,它解开了含在其中的信息。该机制工作的细节可以查询<a href="../5.RRefMovSemPerfForw/item28.html">Item28</a>。</p>
|
||
<p>考虑到<code>std::move</code>和<code>std::forward</code>都可以归结于转换,它们唯一的区别就是<code>std::move</code>总是执行转换,而<code>std::forward</code>偶尔为之。你可能会问是否我们可以免于使用<code>std::move</code>而在任何地方只使用<code>std::forward</code>。 从纯技术的角度,答案是yes:<code>std::forward</code>是可以完全胜任,<code>std::move</code>并非必须。当然,其实两者中没有哪一个函数是<strong>真的必须</strong>的,因为我们可以到处直接写转换代码,但是我希望我们能同意:这将相当的,嗯,让人恶心。</p>
|
||
<p><code>std::move</code>的吸引力在于它的便利性:减少了出错的可能性,增加了代码的清晰程度。考虑一个类,我们希望统计有多少次移动构造函数被调用了。我们只需要一个<code>static</code>的计数器,它会在移动构造的时候自增。假设在这个类中,唯一一个非静态的数据成员是<code>std::string</code>,一种经典的移动构造函数(即,使用<code>std::move</code>)可以被实现如下:</p>
|
||
<pre><code class="language-cpp">class Widget {
|
||
public:
|
||
Widget(Widget&& rhs)
|
||
: s(std::move(rhs.s))
|
||
{ ++moveCtorCalls; }
|
||
|
||
…
|
||
|
||
private:
|
||
static std::size_t moveCtorCalls;
|
||
std::string s;
|
||
};
|
||
</code></pre>
|
||
<p>如果要用<code>std::forward</code>来达成同样的效果,代码可能会看起来像:</p>
|
||
<pre><code class="language-cpp">class Widget{
|
||
public:
|
||
Widget(Widget&& rhs) //不自然,不合理的实现
|
||
: s(std::forward<std::string>(rhs.s))
|
||
{ ++moveCtorCalls; }
|
||
|
||
…
|
||
|
||
}
|
||
</code></pre>
|
||
<p>注意,第一,<code>std::move</code>只需要一个函数实参(<code>rhs.s</code>),而<code>std::forward</code>不但需要一个函数实参(<code>rhs.s</code>),还需要一个模板类型实参<code>std::string</code>。其次,我们传递给<code>std::forward</code>的类型应当是一个non-reference,因为惯例是传递的实参应该是一个右值(见<a href="../5.RRefMovSemPerfForw/item28.html">Item28</a>)。同样,这意味着<code>std::move</code>比起<code>std::forward</code>来说需要打更少的字,并且免去了传递一个表示我们正在传递一个右值的类型实参。同样,它根绝了我们传递错误类型的可能性(例如,<code>std::string&</code>可能导致数据成员<code>s</code>被复制而不是被移动构造)。</p>
|
||
<p>更重要的是,<code>std::move</code>的使用代表着无条件向右值的转换,而使用<code>std::forward</code>只对绑定了右值的引用进行到右值转换。这是两种完全不同的动作。前者是典型地为了移动操作,而后者只是传递(亦为转发)一个对象到另外一个函数,保留它原有的左值属性或右值属性。因为这些动作实在是差异太大,所以我们拥有两个不同的函数(以及函数名)来区分这些动作。</p>
|
||
<p><strong>请记住:</strong></p>
|
||
<ul>
|
||
<li><code>std::move</code>执行到右值的无条件的转换,但就自身而言,它不移动任何东西。</li>
|
||
<li><code>std::forward</code>只有当它的参数被绑定到一个右值时,才将参数转换为右值。</li>
|
||
<li><code>std::move</code>和<code>std::forward</code>在运行期什么也不做。</li>
|
||
</ul>
|
||
|
||
</main>
|
||
|
||
<nav class="nav-wrapper" aria-label="Page navigation">
|
||
<!-- Mobile navigation buttons -->
|
||
<a rel="prev" href="../4.SmartPointers/item22.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/item24.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="../4.SmartPointers/item22.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/item24.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>
|