This repository has been archived by the owner on Nov 12, 2021. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 16
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
5ae9928
commit cb740a4
Showing
8 changed files
with
465 additions
and
5 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
了解了第一个使用移动语义的例子之后,本章会对移动语义的基本特征进行讨论。\par |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,88 @@ | ||
\hspace*{\fill} \par %插入空行 | ||
\textbf{2.1 右值引用} | ||
|
||
为了支持移动语义,C++引入了一种新的引用类型:\textit{右值引用}。我们讨论一下这是什么,以及如何使用。\par | ||
|
||
\hspace*{\fill} \par %插入空行 | ||
\textbf{2.1.1 细节部分} | ||
|
||
右值引用使用两个\&号声明。与普通引用一样,右值引用了一个存在的对象,该对象作为初始值传递。但根据语义,右值引用只能引用一个没有名称的临时对象,或使用\textit{std::move()}的对象:\par | ||
|
||
\begin{lstlisting}[caption={}] | ||
std::string returnStringByValue(); // forward declaration | ||
... | ||
std::string s{"hello"}; | ||
... | ||
std::string&& r1{s}; // ERROR | ||
std::string&& r2{std::move(s)}; // OK | ||
std::string&& r3{returnStringByValue()}; // OK, extends lifetime of return value | ||
\end{lstlisting} | ||
|
||
右值引用来自这样一个事实:对象通常只能引用右值,这是类别,用于没有名称的临时对象和使用\textit{std::move()}的对象。\par | ||
|
||
与成功初始化返回值引用一样,引用将返回值的生命周期延长到引用的生命周期结束(普通的const左值引用已经具有此行为)。\par | ||
|
||
用于初始化引用的语法无关紧要。使用等号、大括号或圆括号都可以:\par | ||
|
||
\begin{lstlisting}[caption={}] | ||
std::string s{"hello"}; | ||
... | ||
std::string&& r1 = std::move(s); // OK, rvalue reference to s | ||
std::string&& r2{std::move(s)}; // OK, rvalue reference to s | ||
std::string&& r3(std::move(s)); // OK, rvalue reference to s | ||
\end{lstlisting} | ||
|
||
所有这些引用都具有这样的语义:“只要对象的状态有效,就可以窃取/修改引用的对象。”编译器不会检查这些语义,因此可以像对该类型的任何非const对象那样修改右值引用,也可能不做修改。如果对一个对象有一个右值引用,该对象可能会收到一个不同的值(可能是也可能不是一个默认构造对象的值),或者保留原始值。\par | ||
|
||
移动语义允许我们使用不再需要的值进行优化。如果编译器自动检测到从一个生命周期结束的对象中使用了一个值,将自动切换到移动语义:\par | ||
|
||
\begin{itemize} | ||
\item 传递一个临时对象的值,该对象将在语句执行后自动撤销。 | ||
\item 传递一个使用\textit{std::move()}的非const对象。 | ||
\end{itemize} | ||
|
||
\hspace*{\fill} \par %插入空行 | ||
\textbf{2.1.2 作为参数的右值引用} | ||
|
||
当我们将形参声明为右值引用时,它具有的行为和语义:\par | ||
|
||
\begin{itemize} | ||
\item 形参只能绑定到一个没有名称的临时对象,或者绑定到一个使用\textit{std::move()}的对象。 | ||
\item 根据右值引用的语义: | ||
\begin{itemize} | ||
\item[-] 调用者不再对值感兴趣。因此,可以修改参数所引用的对象。 | ||
\item[-] 但是,调用者可能仍然对使用对象感兴趣。因此,任何修改都应该使引用的对象保持有效状态。 | ||
\end{itemize} | ||
\end{itemize} | ||
|
||
例如:\par | ||
|
||
\begin{lstlisting}[caption={}] | ||
void foo(std::string&& rv); // takes only objects where we no longer need the value | ||
... | ||
std::string s{"hello"}; | ||
... | ||
foo(s); // ERROR | ||
foo(std::move(s)); // OK, value of s might change | ||
foo(returnStringByValue()); // OK | ||
\end{lstlisting} | ||
|
||
你可以在通过\textit{std::move()}传递命名对象后使用,但通常不这样做。推荐的编程方式是不在\textit{std::move()}后面使用对象:\par | ||
|
||
\begin{lstlisting}[caption={}] | ||
void foo(std::string&& rv); // takes only objects where we no longer need the value | ||
... | ||
std::string s{"hello"}; | ||
... | ||
foo(std::move(s)); // OK, value of s might change | ||
std::cout << s << '\n'; // OOPS, you don’t know which value is printed | ||
foo(std::move(s)); // OOPS, you don’t know which value is passed | ||
s = "hello again"; // OK, but rarely done | ||
foo(std::move(s)); // OK, value of s might change | ||
\end{lstlisting} | ||
|
||
对于标记为“OOPS”的两行,只要不对s的当前值有期望,调用在技术上是可以的。因此,打印是可以的。\par | ||
|
||
|
||
|
||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,94 @@ | ||
如果有一个对象,当使用的时候,生存期没有结束,可以用\textit{std::move()}标记它,表示“在这里不再需要这个值。”std::move()不进行移动,它只在使用表达式的上下文中设置一个临时标记:\par | ||
|
||
\begin{lstlisting}[caption={}] | ||
void foo1(const std::string& lr); // binds to the passed object without modifying it | ||
void foo1(std::string&& rv); // binds to the passed object and might steal/modify the value | ||
... | ||
std::string s{"hello"}; | ||
... | ||
foo1(s); // calls the first foo1(), s keeps its value | ||
foo1(std::move(s)); // calls the second foo1(), s might lose its value | ||
\end{lstlisting} | ||
|
||
带有\textit{std::move()}标记的对象仍然可以传递给接受普通const左值引用的函数:\par | ||
|
||
\begin{lstlisting}[caption={}] | ||
void foo2(const std::string& lr); // binds to the passed object without modifying it | ||
... // no other overload of foo2() | ||
std::string s{"hello"}; | ||
... | ||
foo2(s); // calls foo2(), s keeps its value | ||
foo2(std::move(s)); // also calls foo2(), s keeps its value | ||
\end{lstlisting} | ||
|
||
注意,用\textit{std::move()}标记的对象不能传递给非const左值引用:\par | ||
|
||
\begin{lstlisting}[caption={}] | ||
void foo3(std::string&); // modifies the passed argument | ||
... | ||
std::string s{"hello"}; | ||
... | ||
foo3(s); // OK, calls foo3() | ||
foo3(std::move(s)); // ERROR: no matching foo3() declared | ||
\end{lstlisting} | ||
|
||
注意,用\textit{std::move()}标记马上会销毁的对象是没有意义的。事实上,这甚至会对优化产生反效果。 | ||
|
||
\hspace*{\fill} \par %插入空行 | ||
\textbf{2.2.1 std::move()的头文件} | ||
|
||
std::move()定义为C++标准库中的一个函数。因此,使用时需要包含头文件<utility>:\par | ||
|
||
\begin{lstlisting}[caption={}] | ||
#include <utility> // for std::move() | ||
\end{lstlisting} | ||
|
||
使用\textit{std::move()}的程序在编译时通常不包含这个头文件,因为几乎所有的头文件都包含了<utility>。但是,非标准头文件需要包含该头文件。因此,当使用\textit{std::move()}时,应该显式包含<utility>以使程序可移植。\par | ||
|
||
\hspace*{\fill} \par %插入空行 | ||
\textbf{2.2.2 实现std::move()} | ||
|
||
\textit{std::move()}只不过是对右值引用的static\_cast。可以手动调用static\_cast来达到相同的效果,如下所示:\par | ||
|
||
\begin{lstlisting}[caption={}] | ||
foo(static_cast<decltype(obj)&&>(obj)); // same effect as foo(std::move(obj)) | ||
\end{lstlisting} | ||
|
||
因此,我们也可以这样写:\par | ||
|
||
\begin{lstlisting}[caption={}] | ||
std::string s; | ||
... | ||
foo(static_cast<std::string&&>(s)); // same effect as foo(std::move(s)) | ||
\end{lstlisting} | ||
|
||
注意,static\_cast所做的不仅仅是改变对象的类型。它还允许将对象传递给右值引用(记住,通常不允许将具有名称的对象传递给右值引用)。我们将在关于价值类别的章节中详细讨论。\par | ||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,88 @@ | ||
在\textit{std::move()}之后,移动的对象不会(部分)销毁。它们仍然是有效的对象,至少会为其调用析构函数。然而,它们也应该是有效的,因为它们具有一致的状态,所有操作都按照预期工作,但不知道的是它们的值。这就像使用类型的参数,而不知道传递了哪个值。\par | ||
|
||
\hspace*{\fill} \par %插入空行 | ||
\textbf{2.3.1 有效但定义的状态} | ||
|
||
C++标准库保证移动的对象处于有效但未定义的状态。\par | ||
|
||
考虑如下代码:\par | ||
|
||
\begin{lstlisting}[caption={}] | ||
std::string s; | ||
... | ||
coll.push_back(std::move(s)); | ||
\end{lstlisting} | ||
|
||
用\textit{std::move()}传递s之后,可以获取字符数量,打印相应的值,甚至赋一个新值。如果不先检查字符数量,就不能打印第一个字符或任何其他字符:\par | ||
|
||
\begin{lstlisting}[caption={}] | ||
foo(std::move(s)); // keeps s in a valid but unclear state | ||
|
||
std::cout << s << '\n'; // OK (don’t know which value is written) | ||
std::cout << s.size() << '\n'; // OK (writes current number of characters) | ||
std::cout << s[0] << '\n'; // ERROR (potentially undefined behavior) | ||
std::cout << s.front() << '\n'; // ERROR (potentially undefined behavior) | ||
s = "new value"; // OK | ||
\end{lstlisting} | ||
|
||
尽管不知道具体的值,但该字符串处于一致状态。例如,\textit{s.size()}将返回字符数,可以遍历所有有效的索引:\par | ||
|
||
\begin{lstlisting}[caption={}] | ||
foo(std::move(s)); // keeps s in a valid but unclear state | ||
|
||
for (int i = 0; i < s.size(); ++i) { | ||
std::cout << s[i]; // OK | ||
} | ||
\end{lstlisting} | ||
|
||
对于用户定义类型,还应该确保移动的对象处于有效状态,这有时需要声明或实现移动操作。“移动后的状态”这一章将对此进行详细讨论。\par | ||
|
||
\hspace*{\fill} \par %插入空行 | ||
\textbf{2.3.2 重用移动的对象} | ||
|
||
您可能想知道为什么已移动的对象仍然是有效的对象,并且没有(部分)被销毁。原因是有一些有用的移动语义应用,在这些应用中再次使用已移动的对象是有意义的。\par | ||
|
||
例如,考虑从流中逐行读取字符串并将其移动到vector对象中的代码:\par | ||
|
||
\begin{lstlisting}[caption={}] | ||
std::vector<std::string> allRows; | ||
std::string row; | ||
while (std::getline(myStream, row)) { // read next line into row | ||
allRows.push_back(std::move(row)); // and move it to somewhere | ||
} | ||
\end{lstlisting} | ||
|
||
每次将一行读入行后,使用\textit{std::move()}将\textit{row}的值移动到所有行的向量中。然后,s\textit{td::getline()}再次使用已移动的对象行来读入下一行。\par | ||
|
||
第二个例子,考虑一个交换两个值的泛型函数:\par | ||
|
||
\begin{lstlisting}[caption={}] | ||
template<typename T> | ||
void swap(T& a, T& b) | ||
{ | ||
T tmp{std::move(a)}; | ||
a = std::move(b); // assign new value to moved-from a | ||
b = std::move(tmp); // assign new value to moved-from b | ||
} | ||
\end{lstlisting} | ||
|
||
我们将a的值移动到一个临时对象中,以便之后能够移动并赋值b的值。然后,被移动的对象b接收到tmp的值,这是a之前的值。\par | ||
|
||
例如,在排序算法中使用了这样的代码,我们移动不同元素的值,使它们处于有序状态。给已移动的对象赋新值总是发生在那里。该算法甚至可以对这种移动对象的方式使用排序。\par | ||
|
||
通常,已移动的对象应该是可以撤销的有效对象(析构函数不应该失败),重用以获得其他值,并支持类型的所有操作对象,而不需要知道具体值。“移动后的状态”这一章将对此进行详细讨论。\par | ||
|
||
\hspace*{\fill} \par %插入空行 | ||
\textbf{2.3.3 将对象赋值给它们自己} | ||
|
||
已移动的对象处于有效但未定义状态的规则,通常也适用于直接或间接自移动的对象。\par | ||
|
||
例如,下面的语句之后,对象x通常在不知道其值的情况下是有效的:\par | ||
|
||
\begin{lstlisting}[caption={}] | ||
x = std::move(x); // afterwards x is valid but has an unclear value | ||
\end{lstlisting} | ||
|
||
同样,C++标准库保证对用户定义类型的实例对象通常也提供这种保证,但有时必须实现一些东西来修复默认生成的移动状态。\par | ||
|
||
|
Oops, something went wrong.