2022-06-30 10:23:03 +08:00
<!DOCTYPE HTML>
< html lang = "zh" class = "sidebar-visible no-js light" >
< head >
<!-- Book generated using mdBook -->
< meta charset = "UTF-8" >
2022-07-02 13:21:48 +08:00
< title > Item 23:理解std::move和std::forward - Effective Modern C++< / title >
2022-06-30 10:23:03 +08:00
<!-- 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" >
2022-11-18 22:12:20 +08:00
< 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
2022-06-30 10:23:03 +08:00
< / 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 >
2022-09-08 16:47:52 +08:00
< 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 >
2022-06-30 10:23:03 +08:00
< / 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 >
2022-09-05 16:24:07 +08:00
< p > 形参< code > w< / code > 是一个左值, 即使它的类型是一个rvalue-reference-to-< code > Widget< / code > 。(如果这里震惊到你了,请重新回顾从本书< a href = "https://github.com/CnTransGroup/EffectiveModernCppChinese/blob/master/src/Introduction.md" > 简介< / a > 开始的关于左值和右值的总览。)< / p >
2022-06-30 10:23:03 +08:00
< 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 >
2022-11-25 13:43:31 +08:00
< 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 >
2022-06-30 10:23:03 +08:00
< 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 >
2022-11-25 13:43:31 +08:00
< p > 事实上,右值只不过< strong > 经常< / strong > 是移动操作的候选者。假设你有一个类,它用来表示一段注解。这个类的构造函数接受一个包含有注解的< code > std::string< / code > 作为形参,然后它复制该形参到数据成员。假设你了解< a href = "../8.Tweaks/item41.html" > Item41< / a > ,你声明一个值传递的形参:< / p >
2022-06-30 10:23:03 +08:00
< 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 >
2022-11-25 13:43:31 +08:00
< p > 当复制< code > text< / code > 到一个数据成员的时候,为了避免一次复制操作的代价,你仍然记得来自< a href = "../8.Tweaks/item41.html" > Item41< / a > 的建议,把< code > std::move< / code > 应用到< code > text< / code > 上,因此产生一个右值:< / p >
2022-06-30 10:23:03 +08:00
< 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 >
2022-11-25 13:43:31 +08:00
< 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 >
2022-06-30 10:23:03 +08:00
< 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 >
2022-11-25 13:43:31 +08:00
< 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 >
2022-06-30 10:23:03 +08:00
< 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 >