EffectiveModernCppChinese/5.RRefMovSemPerfForw/item23.html
2023-05-06 06:19:14 +00:00

324 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 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::async</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>
<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&amp;&amp;</code>”也并非总是代表一个右值引用。</p>
<p>无论你挖掘这些特性有多深它们看起来总是还有更多隐藏起来的部分。幸运的是它们的深度总是有限的。本章将会带你到最基础的部分。一旦到达C++11的这部分特性将会具有非常大的意义。比如你会掌握<code>std::move</code><code>std::forward</code>的惯用法。你能够适应“<code>type&amp;&amp;</code>”的歧义性质。你会理解移动操作的令人惊奇的不同表现的背后真相。这些片段都会豁然开朗。在这一点上,你会重新回到一开始的状态,因为移动语义、完美转发和右值引用都会又一次显得直截了当。但是这一次,它们不再使人困惑。</p>
<p>在本章的这些小节中,非常重要的一点是要牢记形参永远是<strong>左值</strong>,即使它的类型是一个右值引用。比如,假设</p>
<pre><code class="language-c++">void f(Widget&amp;&amp; w);
</code></pre>
<p>形参<code>w</code>是一个左值即使它的类型是一个rvalue-reference-to-<code>Widget</code>。(如果这里震惊到你了,请重新回顾从本书<a href="../Introduction.html">简介</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&lt;typename T&gt; //在std命名空间
typename remove_reference&lt;T&gt;::type&amp;&amp;
move(T&amp;&amp; param)
{
using ReturnType = //别名声明见条款9
typename remove_reference&lt;T&gt;::type&amp;&amp;;
return static_cast&lt;ReturnType&gt;(param);
}
</code></pre>
<p>我为你们高亮了这段代码的两部分(译者注:高亮的部分为函数名<code>move</code><code>static_cast&lt;ReturnType&gt;(param)</code>)。一个是函数名字,因为函数的返回值非常具有干扰性,而且我不想你们被它搞得晕头转向。另外一个高亮的部分是包含这段函数的本质的转换。正如你所见,<code>std::move</code>接受一个对象的引用准确的说一个通用引用universal reference<a href="../5.RRefMovSemPerfForw/item24.html">Item24</a>),返回一个指向同对象的引用。</p>
<p>该函数返回类型的<code>&amp;&amp;</code>部分表明<code>std::move</code>函数返回的是一个右值引用,但是,正如<a href="../5.RRefMovSemPerfForw/item28.html">Item28</a>所解释的那样,如果类型<code>T</code>恰好是一个左值引用,那么<code>T&amp;&amp;</code>将会成为一个左值引用。为了避免如此,<em>type trait</em>(见<a href="../3.MovingToModernCpp/item9.html">Item9</a><code>std::remove_reference</code>应用到了类型<code>T</code>上,因此确保了<code>&amp;&amp;</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&lt;typename T&gt;
decltype(auto) move(T&amp;&amp; param) //C++14仍然在std命名空间
{
using ReturnType = remove_referece_t&lt;T&gt;&amp;&amp;;
return static_cast&lt;ReturnType&gt;(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&lt;char&gt;的类型别名
string(const string&amp; rhs); //拷贝构造函数
string(string&amp;&amp; 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&amp; lvalArg); //处理左值
void process(Widget&amp;&amp; rvalArg); //处理右值
template&lt;typename T&gt; //用以转发param到process的模板
void logAndProcess(T&amp;&amp; param)
{
auto now = //获取现在时间
std::chrono::system_clock::now();
makeLogEntry(&quot;Calling 'process'&quot;, now);
process(std::forward&lt;T&gt;(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&amp;&amp; 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&amp;&amp; rhs) //不自然,不合理的实现
: s(std::forward&lt;std::string&gt;(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&amp;</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>