【译】VC10中的C++0x特性 part 2(2):右值引用
来源:
vcblog 作者:Stephan T. Lavavej 翻译:
飘飘白云
(转载时请注明作者和出处。未经许可,请勿用于商业用途)
简介
这一系列文章介绍Microsoft Visual Studio 2010 中支持的C++ 0x特性,目前有三部分。
Part 1 :介绍了Lambdas, 赋予新意义的auto,以及 static_assert;
Part 2( 一 , 二 , 三 ):介绍了右值引用(Rvalue References);
Part 3:介绍了表达式类型(decltype)
VC10中的C++0x特性 Part 1,2,3 译文打包下载(doc 和 pdf 格式): 点此下载
本文为 Part 2 第二页。
move 语意:从 lvalue 移动
现在,如果你喜欢用拷贝赋值函数来实现你的拷贝构造函数该怎样做呢,那你也可能试图用 move 拷贝赋值函数来实现 move 构造函数。这样作是可以的,但是你得小心。下面就是一个错误的实现:
C:\Temp>type unified_wrong.cpp
#include <stddef.h>
#include <iostream>
#include <ostream>
using namespace std;
class remote_integer {
public:
remote_integer() {
cout << "Default constructor." << endl;
m_p = NULL;
}
explicit remote_integer(const int n) {
cout << "Unary constructor." << endl;
m_p = new int(n);
}
remote_integer(const remote_integer& other) {
cout << "Copy constructor." << endl;
m_p = NULL;
*this = other;
}
#ifdef MOVABLE
remote_integer(remote_integer&& other) {
cout << "MOVE CONSTRUCTOR." << endl;
m_p = NULL;
*this = other; // WRONG
}
#endif // #ifdef MOVABLE
remote_integer& operator=(const remote_integer& other) {
cout << "Copy assignment operator." << endl;
if (this != &other) {
delete m_p;
if (other.m_p) {
m_p = new int(*other.m_p);
} else {
m_p = NULL;
}
}
return *this;
}
#ifdef MOVABLE
remote_integer& operator=(remote_integer&& other) {
cout << "MOVE ASSIGNMENT OPERATOR." << endl;
if (this != &other) {
delete m_p;
m_p = other.m_p;
other.m_p = NULL;
}
return *this;
}
#endif // #ifdef MOVABLE
~remote_integer() {
cout << "Destructor." << endl;
delete m_p;
}
int get() const {
return m_p ? *m_p : 0;
}
private:
int * m_p;
};
remote_integer frumple(const int n) {
if (n == 1729) {
return remote_integer(1729);
}
remote_integer ret(n * n);
return ret;
}
int main() {
remote_integer x = frumple(5);
cout << x.get() << endl;
remote_integer y = frumple(1729);
cout << y.get() << endl;
}
C:\Temp>cl /EHsc /nologo /W4 /O2 unified_wrong.cpp
unified_wrong.cpp
C:\Temp>unified_wrong
Unary constructor.
Copy constructor.
Copy assignment operator.
Destructor.
25
Unary constructor.
1729
Destructor.
Destructor.
C:\Temp>cl /EHsc /nologo /W4 /O2 /DMOVABLE unified_wrong.cpp
unified_wrong.cpp
C:\Temp>unified_wrong
Unary constructor.
MOVE CONSTRUCTOR.
Copy assignment operator.
Destructor.
25
Unary constructor.
1729
Destructor.
Destructor.
(编译器在这里进行了返回值优化(RVO),但不是具名返回值优化(NRVO)。就像我之前提到的,有些拷贝构造函数被 RVO 或 NRVO 优化掉了,但编译器并不总是能够做这样的优化,这时剩余的就由 move 构造函数来优化。)
move 构造函数中标记为 WRONG 的那一行,调用了拷贝赋值函数,编译能通过也能运行,但这违背了 move 构造函数的本意。(译注:因为那个拷贝赋值函数只是进行普通的拷贝赋值,而不是 move 赋值!)
这是怎么回事呢?记住:在C++98/03中,具名 lvalue 引用是左值(给定语句 int& r = *p; r 是 lvalue),不具名 lvalue 引用还是左值(给定语句 vector<int> v(10, 1729), v[0] 返回 int&, 你可以对这个不具名 lvalue 引用取址)。但是 rvalue 引用就不一样了:
具名 lvalue 引用是 lvalue。
不具名 rvalue 引用是 rvalue。
一个具名 rvalue 引用是一个 lvalue 是因为可以对它施加多重操作,重复使用。相反,如果它是一个 ravlue 的话,那么对它施加的第一个操作能够“窃取”它,而后续操作就没机会了。这里的“窃取”是说不会被察觉到,所以这是行不通的。另一方面,不具名 rvalue 引用不能被重复使用,所以它仍保持右值(rvalueness)语意。
如果你真的打算用 move 赋值函数来实现 move 构造函数,你需要从 lvalue move,就像是从 rvalue move 一样。C++0x <utility> 中的 std::move() 具备这样的能力,VC10将会有这个(实际上,开发版中已经有了),但VC10 TCP版还没有,所以我会教你从头做起:
C:\Temp>type unified_right.cpp
#include <stddef.h>
#include <iostream>
#include <ostream>
using namespace std;
template <typename T> struct RemoveReference {
typedef T type;
};
template <typename T> struct RemoveReference<T&> {
typedef T type;
};
template <typename T> struct RemoveReference<T&&> {
typedef T type;
};
template <typename T> typename RemoveReference<T>::type&& Move(T&& t) {
return t;
}
class remote_integer {
public:
remote_integer() {
cout << "Default constructor." << endl;
m_p = NULL;
}
explicit remote_integer(const int n) {
cout << "Unary constructor." << endl;
m_p = new int(n);
}
remote_integer(const remote_integer& other) {
cout << "Copy constructor." << endl;
m_p = NULL;
*this = other;
}
#ifdef MOVABLE
remote_integer(remote_integer&& other) {
cout << "MOVE CONSTRUCTOR." << endl;
m_p = NULL;
*this = Move(other); // RIGHT
}
#endif // #ifdef MOVABLE
remote_integer& operator=(const remote_integer& other) {
cout << "Copy assignment operator." << endl;
if (this != &other) {
delete m_p;
if (other.m_p) {
m_p = new int(*other.m_p);
} else {
m_p = NULL;
}
}
return *this;
}
#ifdef MOVABLE
remote_integer& operator=(remote_integer&& other) {
cout << "MOVE ASSIGNMENT OPERATOR." << endl;
if (this != &other) {
delete m_p;
m_p = other.m_p;
other.m_p = NULL;
}
return *this;
}
#endif // #ifdef MOVABLE
~remote_integer() {
cout << "Destructor." << endl;
delete m_p;
}
int get() const {
return m_p ? *m_p : 0;
}
private:
int * m_p;
};
remote_integer frumple(const int n) {
if (n == 1729) {
return remote_integer(1729);
}
remote_integer ret(n * n);
return ret;
}
int main() {
remote_integer x = frumple(5);
cout << x.get() << endl;
remote_integer y = frumple(1729);
cout << y.get() << endl;
}
C:\Temp>cl /EHsc /nologo /W4 /O2 /DMOVABLE unified_right.cpp
unified_right.cpp
C:\Temp>unified_right
Unary constructor.
MOVE CONSTRUCTOR.
MOVE ASSIGNMENT OPERATOR.
Destructor.
25
Unary constructor.
1729
Destructor.
Destructor.
(我将交替使用 std::move() 和我自己的 Move(),因为它们的实现是等价的) std::move() 是怎样工作的呢?目前,我只能跟你说这是“魔法”。(后面会有完整的解释,并不复杂,但它与模板参数推导和引用折叠(reference collapsing,译注:引用的引用)有 关,后面讲完美转发的时候我们还会遇到这两个东西)。我可以用一个具体的例子来略过“魔法”:给定一个 string 类型的左值,像前面重载决议例子中的 up ,std::move(up) 调用 string&& std::move(string&),这个函数返回一个不具名的 rvalue 引用,它是一个 rvalue。给定一个 string 类型的 rvalue,像前面重载决议例子中的 strange(), std::move(strange()) 调用 string&& std::move(string&&),同样这个函数还是返回一个不具名的 rvalue,还是 rvalue。
std::move() 除了让你能用 move 复制函数来实现 move 构造函数之外,还能在其他地方发挥作用。无论何时,只要你有一个左值,而它的值也不再重要了(例如,它将被销毁或被赋值),你就可以使用 std::move(你的左值表达式) 来使用 move 语意。
move 语意:可移动成员(movable member)
C++0x 的标准类型(像 vector, string, regex) 都有 move 构造函数和 move 赋值函数。而且我们也已经看到了如何在我们自己的类中通过手动管理资源来实现 move 语意(像前面的 remote_integer 类)。如果类中包含可移动数据成员(像 vector, string, regex, remote_integer )时该怎么办呢?编译器不会自动帮我们自动产生 move 构造函数和 move 赋值函数,所以我们必须手动编写它们。很幸运,有了 std::move() 编写它们是很容易的。
C:\Temp>type point.cpp
#include <stddef.h>
#include <iostream>
#include <ostream>
using namespace std;
template <typename T> struct RemoveReference {
typedef T type;
};
template <typename T> struct RemoveReference<T&> {
typedef T type;
};
template <typename T> struct RemoveReference<T&&> {
typedef T type;
};
template <typename T> typename RemoveReference<T>::type&& Move(T&& t) {
return t;
}
class remote_integer {
public:
remote_integer() {
cout << "Default constructor." << endl;
m_p = NULL;
}
explicit remote_integer(const int n) {
cout << "Unary constructor." << endl;
m_p = new int(n);
}
remote_integer(const remote_integer& other) {
cout << "Copy constructor." << endl;
if (other.m_p) {
m_p = new int(*other.m_p);
} else {
m_p = NULL;
}
}
remote_integer(remote_integer&& other) {
cout << "MOVE CONSTRUCTOR." << endl;
m_p = other.m_p;
other.m_p = NULL;
}
remote_integer& operator=(const remote_integer& other) {
cout << "Copy assignment operator." << endl;
if (this != &other) {
delete m_p;
if (other.m_p) {
m_p = new int(*other.m_p);
} else {
m_p = NULL;
}
}
return *this;
}
remote_integer& operator=(remote_integer&& other) {
cout << "MOVE ASSIGNMENT OPERATOR." << endl;
if (this != &other) {
delete m_p;
m_p = other.m_p;
other.m_p = NULL;
}
return *this;
}
~remote_integer() {
cout << "Destructor." << endl;
delete m_p;
}
int get() const {
return m_p ? *m_p : 0;
}
private:
int * m_p;
};
class remote_point {
public:
remote_point(const int x_arg, const int y_arg)
: m_x(x_arg), m_y(y_arg) { }
remote_point(remote_point&& other)
: m_x(Move(other.m_x)),
m_y(Move(other.m_y)) { }
remote_point& operator=(remote_point&& other) {
m_x = Move(other.m_x);
m_y = Move(other.m_y);
return *this;
}
int x() const { return m_x.get(); }
int y() const { return m_y.get(); }
private:
remote_integer m_x;
remote_integer m_y;
};
remote_point five_by_five() {
return remote_point(5, 5);
}
remote_point taxicab(const int n) {
if (n == 0) {
return remote_point(1, 1728);
}
remote_point ret(729, 1000);
return ret;
}
int main() {
remote_point p = taxicab(43112609);
cout << "(" << p.x() << ", " << p.y() << ")" << endl;
p = five_by_five();
cout << "(" << p.x() << ", " << p.y() << ")" << endl;
}
C:\Temp>cl /EHsc /nologo /W4 /O2 point.cpp
point.cpp
C:\Temp>point
Unary constructor.
Unary constructor.
MOVE CONSTRUCTOR.
MOVE CONSTRUCTOR.
Destructor.
Destructor.
(729, 1000)
Unary constructor.
Unary constructor.
MOVE ASSIGNMENT OPERATOR.
MOVE ASSIGNMENT OPERATOR.
Destructor.
Destructor.
(5, 5)
Destructor.
Destructor.
现在你看到啦,按成员移动(memberwise move)是很容易做到的。注意, remote_point 的 move 赋值函数没有进行自我赋值检查,是因为 remote_integer 已经检查过了。也要注意到 remote_point 隐式声明的拷贝构造函数,拷贝赋值函数和析构函数都正常运作。
到现在,你应该对 move 语意已经非常熟悉了。(希望不是抓狂啊!)为了测试你新获得的这个不可思议的技能,请为前面的例子写一个 +() 操作符函数当作练习吧。
最后的提醒:只要你的类支持 move 语意,你就应该实现 move 构造函数和 move 赋值函数。因为不仅仅是你平常使用这些类时可从 move 语意中获利, STL 容器和算法也能从中获利,通过廉价的 move 省下昂贵的拷贝开销。
(转载请注明出处,作者与译者信息,请勿用于商业用途)
< 前一页 后一页 >