252 lines
30 KiB
HTML
252 lines
30 KiB
HTML
<!DOCTYPE html>
|
|
<html lang="zh" dir="ltr" class="client-nojs" xmlns="http://www.w3.org/1999/xhtml" xml:lang="zh">
|
|
<head><meta http-equiv="x-ua-compatible" content="ie=edge">
|
|
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
|
|
<title>复制消除</title>
|
|
<link rel="stylesheet" href="ext.css" />
|
|
<link rel="stylesheet" href="site_modules.css" />
|
|
</head>
|
|
<body class="mediawiki ltr sitedir-ltr ns-0 ns-subject page-cpp_language_copy_elision skin-cppreference2 action-view cpp-navbar">
|
|
<div id="cpp-content-base">
|
|
<div id="content"><a id="top"></a>
|
|
<h1 id="firstHeading" class="firstHeading">复制消除</h1>
|
|
<div id="bodyContent">
|
|
<div id="contentSub"><span class="subpages">< <a href="cpp.html">cpp</a>‎ | <a href="cpp-language.html">language</a></span></div>
|
|
<div id="mw-content-text" lang="zh" dir="ltr" class="mw-content-ltr" xml:lang="zh">
|
|
<p>省略复制<span class="t-rev-inl t-since-cxx11"><span>及移动</span> <span><span class="t-mark-rev t-since-cxx11">(C++11 起)</span></span></span>构造函数,导致零复制的按值传递语义。</p>
|
|
<h3><span class="mw-headline" id=".E8.A7.A3.E9.87.8A">解释</span></h3>
|
|
<table class="t-rev-begin">
|
|
<tr class="t-rev t-since-cxx17">
|
|
<td>
|
|
<h4><span class="mw-headline" id=".E5.BC.BA.E5.88.B6.E7.9A.84.E5.A4.8D.E5.88.B6.2F.E7.A7.BB.E5.8A.A8.E6.93.8D.E4.BD.9C.E6.B6.88.E9.99.A4">强制的复制/移动操作消除</span></h4>
|
|
<p>以下情况要求编译器省略类对象的复制和移动构造,即使复制/移动构造函数和析构函数拥有可观察的副作用。这些对象将直接构造到它们本来要复制/移动到的存储中。复制/移动构造函数不必存在或可访问:</p>
|
|
<ul>
|
|
<li>在 <a href="cpp-language-return.html">return 语句</a>中,当操作数是一个与函数返回类型相同的类类型的<a href="cpp-language-value_category.html">纯右值</a>(忽略 <a href="cpp-language-cv.html">cv 限定</a>)时:</li>
|
|
</ul>
|
|
<div dir="ltr" class="mw-geshi" style="text-align: left;">
|
|
<div class="cpp source-cpp">
|
|
<pre class="de1">T f<span class="br0">(</span><span class="br0">)</span> <span class="br0">{</span>
|
|
<span class="kw1">return</span> T<span class="br0">(</span><span class="br0">)</span><span class="sy4">;</span>
|
|
<span class="br0">}</span>
|
|
|
|
f<span class="br0">(</span><span class="br0">)</span><span class="sy4">;</span> <span class="co1">// 仅调用一次 T 的默认构造函数</span></pre></div>
|
|
</div>
|
|
<dl>
|
|
<dd>返回类型的析构函数必须在 return 语句位置可访问且未被删除,即使没有 T 对象要被销毁。</dd>
|
|
</dl>
|
|
<ul>
|
|
<li>在对象的初始化中,当初始化器表达式是一个与变量类型相同的类类型的<a href="cpp-language-value_category.html">纯右值</a>(忽略 <a href="cpp-language-cv.html">cv 限定</a>)时:</li>
|
|
</ul>
|
|
<div dir="ltr" class="mw-geshi" style="text-align: left;">
|
|
<div class="cpp source-cpp">
|
|
<pre class="de1">T x <span class="sy1">=</span> T<span class="br0">(</span>T<span class="br0">(</span>f<span class="br0">(</span><span class="br0">)</span><span class="br0">)</span><span class="br0">)</span><span class="sy4">;</span> <span class="co1">// 仅调用一次 T 的默认构造函数以初始化 x</span></pre></div>
|
|
</div>
|
|
<dl>
|
|
<dd>只能在已知要初始化的对象不是可能重叠的子对象时应用此规则:</dd>
|
|
</dl>
|
|
<div dir="ltr" class="mw-geshi" style="text-align: left;">
|
|
<div class="cpp source-cpp">
|
|
<pre class="de1"><span class="kw1">struct</span> C <span class="br0">{</span> <span class="coMULTI">/* ... */</span> <span class="br0">}</span><span class="sy4">;</span>
|
|
C f<span class="br0">(</span><span class="br0">)</span><span class="sy4">;</span>
|
|
<span class="kw1">struct</span> D<span class="sy4">;</span>
|
|
D g<span class="br0">(</span><span class="br0">)</span><span class="sy4">;</span>
|
|
<span class="kw1">struct</span> D <span class="sy4">:</span> C <span class="br0">{</span>
|
|
D<span class="br0">(</span><span class="br0">)</span> <span class="sy4">:</span> C<span class="br0">(</span>f<span class="br0">(</span><span class="br0">)</span><span class="br0">)</span> <span class="br0">{</span> <span class="br0">}</span> <span class="co1">// 初始化基类子对象时无消除</span>
|
|
D<span class="br0">(</span><span class="kw4">int</span><span class="br0">)</span> <span class="sy4">:</span> D<span class="br0">(</span>g<span class="br0">(</span><span class="br0">)</span><span class="br0">)</span> <span class="br0">{</span> <span class="br0">}</span> <span class="co1">// 无消除,因为正在初始化的 D 对象可能是某个其他类的基类子对象</span>
|
|
<span class="br0">}</span><span class="sy4">;</span></pre></div>
|
|
</div>
|
|
<p>注意:上述规则指定的不是优化:针对<a href="cpp-language-value_category.html">纯右值</a>和<a href="cpp-language-implicit_conversion.html#.E4.B8.B4.E6.97.B6.E9.87.8F.E5.AE.9E.E8.B4.A8.E5.8C.96">临时量</a>的 C++17 核心语言规定在本质上不同于之前的 C++ 版本:不再有临时量用于复制/移动。描述 C++17 语义的另一种方式是“传递未实质化的值”:返回并使用纯右值时不实质化临时量。</p>
|
|
</td>
|
|
<td><span class="t-mark-rev t-since-cxx17">(C++17 起)</span></td>
|
|
</tr>
|
|
</table>
|
|
<h4><span class="mw-headline" id=".E9.9D.9E.E5.BC.BA.E5.88.B6.E7.9A.84.E5.A4.8D.E5.88.B6.2F.E7.A7.BB.E5.8A.A8_.28C.2B.2B11_.E8.B5.B7.29.E6.93.8D.E4.BD.9C.E6.B6.88.E9.99.A4">非强制的复制<span class="t-rev-inl t-since-cxx11"><span>/移动</span> <span><span class="t-mark-rev t-since-cxx11">(C++11 起)</span></span></span>操作消除</span></h4>
|
|
<p>下列环境下,允许但不要求编译器省略类对象的复制<span class="t-rev-inl t-since-cxx11"><span>和移动</span> <span><span class="t-mark-rev t-since-cxx11">(C++11 起)</span></span></span>构造,即使复制<span class="t-rev-inl t-since-cxx11"><span>/移动</span> <span><span class="t-mark-rev t-since-cxx11">(C++11 起)</span></span></span>构造函数和析构函数拥有可观察的副作用。这些对象将直接构造到它们本来要复制/移动到的存储中。这是一项优化:即使进行了优化而不调用复制<span class="t-rev-inl t-since-cxx11"><span>/移动</span> <span><span class="t-mark-rev t-since-cxx11">(C++11 起)</span></span></span>构造函数,它仍然必须存在且可访问(如同完全未发生优化),否则程序非良构:</p>
|
|
<ul>
|
|
<li><a href="cpp-language-return.html">return 语句</a>中,当操作数是拥有自动存储期的非 volatile 对象的名字,该名字不是函数形参或 catch 子句形参,且其具有与函数返回类型相同的类类型(忽略 <a href="cpp-language-cv.html">cv 限定</a>)时。这种复制消除的变体被称为 NRVO,“具名返回值优化 (named return value optimization)”。</li>
|
|
</ul>
|
|
<table class="t-rev-begin">
|
|
<tr class="t-rev t-until-cxx17">
|
|
<td>
|
|
<ul>
|
|
<li>在对象的初始化中,当源对象是无名临时量且与目标对象具有相同类型(忽略 <a href="cpp-language-cv.html">cv 限定</a>)时。当无名临时量为 return 语句的操作数时,称这种复制消除的变体为 RVO,“返回值优化 (return value optimization)”。</li>
|
|
</ul>
|
|
</td>
|
|
<td><span class="t-mark-rev t-until-cxx17">(C++17 前)</span></td>
|
|
</tr>
|
|
<tr class="t-rev t-since-cxx17">
|
|
<td>
|
|
<p>返回值优化是强制要求的,而不再被当做复制消除;见上文。</p>
|
|
</td>
|
|
<td><span class="t-mark-rev t-since-cxx17">(C++17 起)</span></td>
|
|
</tr>
|
|
</table>
|
|
<table class="t-rev-begin">
|
|
<tr class="t-rev t-since-cxx11">
|
|
<td>
|
|
<ul>
|
|
<li><a href="cpp-language-throw.html">throw 表达式</a>中,当操作数是具有自动存储期的非 volatile 对象的名字,其并非函数形参或 catch 子句形参,且其作用域不延伸超过最内层的 try 块(若有 try 块)时。</li>
|
|
<li><a href="cpp-language-try_catch.html">catch 子句</a>中,当实参与抛出的异常对象具有相同类型(忽略 <a href="cpp-language-cv.html">cv 限定</a>)时,省略异常对象的复制,而该 catch 子句体直接访问该异常对象,如同按引用捕获它一样(不可能从异常对象移动,因为它始终是左值)。如果这种复制消除会因为除了跳过该 catch 子句形参的复制构造函数和析构函数之外的任何原因,导致程序的可观察行为发生改变,则它被禁止(例如,当修改了 catch 子句的实参,并以 <span class="t-c"><span class="mw-geshi cpp source-cpp"><span class="kw1">throw</span></span></span> 重新抛出异常对象时)。</li>
|
|
</ul>
|
|
</td>
|
|
<td><span class="t-mark-rev t-since-cxx11">(C++11 起)</span></td>
|
|
</tr>
|
|
<tr class="t-rev t-since-cxx20">
|
|
<td>
|
|
<ul>
|
|
<li>在<a href="cpp-language-coroutines.html">协程</a>中,可以消除将形参向协程状态内的复制/移动,只要除了对形参的构造函数与析构函数的调用被忽略以外,不改变程序的行为即可。这种情形可以在暂停点后始终不使用形参,或者整个协程状态本就始终不在堆上分配时出现。</li>
|
|
</ul>
|
|
</td>
|
|
<td><span class="t-mark-rev t-since-cxx20">(C++20 起)</span></td>
|
|
</tr>
|
|
</table>
|
|
<p>进行复制消除时,实现将被省略的复制<span class="t-rev-inl t-since-cxx11"><span>/移动</span> <span><span class="t-mark-rev t-since-cxx11">(C++11 起)</span></span></span>操作的源和目标单纯地当做指代同一对象的两种不同方式,而该对象将在假如不进行优化时两个对象中后被销毁的对象销毁时销毁<span class="t-rev-inl t-since-cxx17"><span>(但如果被选择的构造函数的形参是对象类型的右值引用,那么该销毁发生于目标对象本应被销毁时)</span> <span><span class="t-mark-rev t-since-cxx17">(C++17 起)</span></span></span>。</p>
|
|
<p>可以连锁多次复制消除,以消除多次复制。</p>
|
|
<table class="t-rev-begin">
|
|
<tr class="t-rev t-since-cxx11">
|
|
<td>
|
|
<ul>
|
|
<li>在<a href="cpp-language-constant_expression.html">常量表达式</a>和<a href="cpp-language-constant_initialization.html">常量初始化</a>中,保证进行返回值优化(RVO),但禁止具名返回值优化(NRVO):</li>
|
|
</ul>
|
|
<div dir="ltr" class="mw-geshi" style="text-align: left;">
|
|
<div class="cpp source-cpp">
|
|
<pre class="de1"><span class="kw1">struct</span> A <span class="br0">{</span>
|
|
<span class="kw4">void</span> <span class="sy2">*</span>p<span class="sy4">;</span>
|
|
<span class="kw4">constexpr</span> A<span class="br0">(</span><span class="br0">)</span><span class="sy4">:</span> p<span class="br0">(</span>this<span class="br0">)</span> <span class="br0">{</span><span class="br0">}</span>
|
|
<span class="br0">}</span><span class="sy4">;</span>
|
|
|
|
<span class="kw4">constexpr</span> A g<span class="br0">(</span><span class="br0">)</span>
|
|
<span class="br0">{</span>
|
|
A a<span class="sy4">;</span>
|
|
<span class="kw1">return</span> a<span class="sy4">;</span>
|
|
<span class="br0">}</span>
|
|
|
|
<span class="kw4">constexpr</span> A a<span class="sy4">;</span> <span class="co1">// a.p 指向 a</span>
|
|
<span class="co1">// constexpr A b = g(); // 错误: b.p 会悬垂,并会指向有自动存储期的临时量</span>
|
|
|
|
<span class="kw4">void</span> h<span class="br0">(</span><span class="br0">)</span>
|
|
<span class="br0">{</span>
|
|
A c <span class="sy1">=</span> g<span class="br0">(</span><span class="br0">)</span><span class="sy4">;</span> <span class="co1">// c.p 可能指向 c 或转瞬即逝的临时量</span>
|
|
<span class="br0">}</span>
|
|
|
|
<span class="kw4">extern</span> <span class="kw4">const</span> A d<span class="sy4">;</span>
|
|
<span class="kw4">constexpr</span> A f<span class="br0">(</span><span class="br0">)</span>
|
|
<span class="br0">{</span>
|
|
A e<span class="sy4">;</span>
|
|
<span class="kw1">if</span> <span class="br0">(</span><span class="sy3">&</span>e <span class="sy1">==</span> <span class="sy3">&</span>d<span class="br0">)</span>
|
|
<span class="kw1">return</span> A<span class="br0">(</span><span class="br0">)</span><span class="sy4">;</span>
|
|
<span class="kw1">else</span>
|
|
<span class="kw1">return</span> e<span class="sy4">;</span>
|
|
<span class="co1">// 在常量求值中强制 NRVO 会导致矛盾,即当且仅当不执行 NRVO 时执行 NRVO</span>
|
|
<span class="br0">}</span>
|
|
<span class="co1">// constexpr A d = f(); // 错误: d.p 悬垂</span></pre></div>
|
|
</div>
|
|
</td>
|
|
<td><span class="t-mark-rev t-since-cxx11">(C++11 起)</span></td>
|
|
</tr>
|
|
</table>
|
|
<h3><span class="mw-headline" id=".E6.B3.A8.E8.A7.A3">注解</span></h3>
|
|
<p>复制消除是允许改变可观察副作用的<span class="t-rev-inl t-until-cxx14"><span>唯一得到允许的优化形式</span> <span><span class="t-mark-rev t-until-cxx14">(C++14 前)</span></span></span><span class="t-rev-inl t-since-cxx14"><span>两种允许的优化形式之一,另一种是<a href="cpp-language-new.html#.E5.88.86.E9.85.8D">分配消除与扩展</a></span> <span><span class="t-mark-rev t-since-cxx14">(C++14 起)</span></span></span>。因为一些编译器并不在所有允许的场合中进行复制消除(例如 debug 模式下),依赖于复制/移动构造函数和析构函数的副作用的程序是不可移植的。</p>
|
|
<table class="t-rev-begin">
|
|
<tr class="t-rev t-since-cxx11">
|
|
<td>
|
|
<p>在 return 语句或 throw 表达式中,如果编译器不能进行复制消除,但满足或本应满足复制消除的条件(不含源是函数形参的情况),即使对象由左值代表,编译器也将尝试使用移动构造函数;细节见 <a href="cpp-language-return.html#.E6.B3.A8.E8.A7.A3">return 语句</a>。</p>
|
|
</td>
|
|
<td><span class="t-mark-rev t-since-cxx11">(C++11 起)</span></td>
|
|
</tr>
|
|
</table>
|
|
<h3><span class="mw-headline" id=".E7.A4.BA.E4.BE.8B">示例</span></h3>
|
|
<div class="t-example">
|
|
<div class="t-example-live-link"></div>
|
|
<div dir="ltr" class="mw-geshi" style="text-align: left;">
|
|
<div class="cpp source-cpp">
|
|
<pre class="de1"><span class="co2">#include <iostream></span>
|
|
<span class="co2">#include <vector></span>
|
|
|
|
<span class="kw1">struct</span> Noisy <span class="br0">{</span>
|
|
Noisy<span class="br0">(</span><span class="br0">)</span> <span class="br0">{</span> <a href="cpp-io-cout.html"><span class="kw1758">std::<span class="me2">cout</span></span></a> <span class="sy1"><<</span> <span class="st0">"在 "</span> <span class="sy1"><<</span> this <span class="sy1"><<</span> <span class="st0">" 构造"</span> <span class="sy1"><<</span> <span class="st0">'<span class="es1">\n</span>'</span><span class="sy4">;</span> <span class="br0">}</span>
|
|
Noisy<span class="br0">(</span><span class="kw4">const</span> Noisy<span class="sy3">&</span><span class="br0">)</span> <span class="br0">{</span> <a href="cpp-io-cout.html"><span class="kw1758">std::<span class="me2">cout</span></span></a> <span class="sy1"><<</span> <span class="st0">"复制构造<span class="es1">\n</span>"</span><span class="sy4">;</span> <span class="br0">}</span>
|
|
Noisy<span class="br0">(</span>Noisy<span class="sy3">&&</span><span class="br0">)</span> <span class="br0">{</span> <a href="cpp-io-cout.html"><span class="kw1758">std::<span class="me2">cout</span></span></a> <span class="sy1"><<</span> <span class="st0">"移动构造<span class="es1">\n</span>"</span><span class="sy4">;</span> <span class="br0">}</span>
|
|
~Noisy<span class="br0">(</span><span class="br0">)</span> <span class="br0">{</span> <a href="cpp-io-cout.html"><span class="kw1758">std::<span class="me2">cout</span></span></a> <span class="sy1"><<</span> <span class="st0">"在 "</span> <span class="sy1"><<</span> this <span class="sy1"><<</span> <span class="st0">" 析构"</span> <span class="sy1"><<</span> <span class="st0">'<span class="es1">\n</span>'</span><span class="sy4">;</span> <span class="br0">}</span>
|
|
<span class="br0">}</span><span class="sy4">;</span>
|
|
|
|
<a href="cpp-container-vector.html"><span class="kw1269">std::<span class="me2">vector</span></span></a><span class="sy1"><</span>Noisy<span class="sy1">></span> f<span class="br0">(</span><span class="br0">)</span> <span class="br0">{</span>
|
|
<a href="cpp-container-vector.html"><span class="kw1269">std::<span class="me2">vector</span></span></a><span class="sy1"><</span>Noisy<span class="sy1">></span> v <span class="sy1">=</span> <a href="cpp-container-vector.html"><span class="kw1269">std::<span class="me2">vector</span></span></a><span class="sy1"><</span>Noisy<span class="sy1">></span><span class="br0">(</span><span class="nu0">3</span><span class="br0">)</span><span class="sy4">;</span> <span class="co1">// 从临时量 (C++17 前)</span>
|
|
<span class="co1">// 从纯右值 (C++17 起)</span>
|
|
<span class="co1">// 初始化 v 中的复制消除</span>
|
|
<span class="kw1">return</span> v<span class="sy4">;</span> <span class="co1">// 从 v 到结果对象的 NRVO(C++17 中仍不保证)</span>
|
|
<span class="br0">}</span> <span class="co1">// 如果禁用优化,则调用移动构造函数</span>
|
|
|
|
<span class="kw4">void</span> g<span class="br0">(</span><a href="cpp-container-vector.html"><span class="kw1269">std::<span class="me2">vector</span></span></a><span class="sy1"><</span>Noisy<span class="sy1">></span> arg<span class="br0">)</span> <span class="br0">{</span>
|
|
<a href="cpp-io-cout.html"><span class="kw1758">std::<span class="me2">cout</span></span></a> <span class="sy1"><<</span> <span class="st0">"arg.size() = "</span> <span class="sy1"><<</span> arg.<span class="me1">size</span><span class="br0">(</span><span class="br0">)</span> <span class="sy1"><<</span> <span class="st0">'<span class="es1">\n</span>'</span><span class="sy4">;</span>
|
|
<span class="br0">}</span>
|
|
|
|
<span class="kw4">int</span> main<span class="br0">(</span><span class="br0">)</span> <span class="br0">{</span>
|
|
<a href="cpp-container-vector.html"><span class="kw1269">std::<span class="me2">vector</span></span></a><span class="sy1"><</span>Noisy<span class="sy1">></span> v <span class="sy1">=</span> f<span class="br0">(</span><span class="br0">)</span><span class="sy4">;</span> <span class="co1">// 从 f() 返回的临时量 (C++17 前)</span>
|
|
<span class="co1">// 从纯右值 f() (C++17 起)</span>
|
|
<span class="co1">// 初始化 v 中的复制消除</span>
|
|
g<span class="br0">(</span>f<span class="br0">(</span><span class="br0">)</span><span class="br0">)</span><span class="sy4">;</span> <span class="co1">// 从 f() 返回的临时量 (C++17 前)</span>
|
|
<span class="co1">// 从纯右值 f() (C++17 起)</span>
|
|
<span class="co1">// 初始化 g() 的形参中的复制消除</span>
|
|
<span class="br0">}</span></pre></div>
|
|
</div>
|
|
<p>可能的输出:</p>
|
|
<div dir="ltr" class="mw-geshi" style="text-align: left;">
|
|
<div class="text source-text">
|
|
<pre class="de1">在 0x2145c20 构造
|
|
在 0x2145c21 构造
|
|
在 0x2145c22 构造
|
|
在 0x2146c50 构造
|
|
在 0x2146c51 构造
|
|
在 0x2146c52 构造
|
|
arg.size() = 3
|
|
在 0x2146c50 析构
|
|
在 0x2146c51 析构
|
|
在 0x2146c52 析构
|
|
在 0x2145c20 析构
|
|
在 0x2145c21 析构
|
|
在 0x2145c22 析构</pre></div>
|
|
</div>
|
|
</div>
|
|
<h3><span class="mw-headline" id=".E7.BC.BA.E9.99.B7.E6.8A.A5.E5.91.8A">缺陷报告</span></h3>
|
|
<p>下列更改行为的缺陷报告追溯地应用于以前出版的 C++ 标准。</p>
|
|
<table class="dsctable" style="font-size:0.8em">
|
|
<tr>
|
|
<th>DR</th>
|
|
<th>应用于</th>
|
|
<th>出版时的行为</th>
|
|
<th>正确行为</th>
|
|
</tr>
|
|
<tr>
|
|
<td><a rel="nofollow" class="external text" href="https://wg21.cmeerw.net/cwg/issue2022">CWG 2022</a></td>
|
|
<td>C++11</td>
|
|
<td>常量表达式中曾允许不进行复制消除</td>
|
|
<td>强制复制消除</td>
|
|
</tr>
|
|
<tr>
|
|
<td><a rel="nofollow" class="external text" href="https://wg21.cmeerw.net/cwg/issue2278">CWG 2278</a></td>
|
|
<td>C++11</td>
|
|
<td>常量表达式中曾强制 NRVO</td>
|
|
<td>在常量表达式中禁止 NRVO</td>
|
|
</tr>
|
|
<tr>
|
|
<td><a rel="nofollow" class="external text" href="https://wg21.cmeerw.net/cwg/issue2426">CWG 2426</a></td>
|
|
<td>C++17</td>
|
|
<td>返回纯右值时不要求析构函数</td>
|
|
<td>潜在调用析构函数</td>
|
|
</tr>
|
|
</table>
|
|
<h3><span class="mw-headline" id=".E5.8F.82.E9.98.85">参阅</span></h3>
|
|
<ul>
|
|
<li><a href="cpp-language-copy_initialization.html">复制初始化</a></li>
|
|
<li><a href="cpp-language-copy_constructor.html">复制构造函数</a></li>
|
|
<li><a href="cpp-language-move_constructor.html">移动构造函数</a></li>
|
|
</ul>
|
|
</div>
|
|
<div class="visualClear"></div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</body>
|
|
</html>
|