【譯】VC10中的C++0x特性 part 2(2):右值引用
來源:
vcblog 作者:Stephan T. Lavavej 翻譯:
飄飄白云
(轉(zhuǎn)載時請注明作者和出處。未經(jīng)許可,請勿用于商業(yè)用途)
簡介
這一系列文章介紹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 移動
現(xiàn)在,如果你喜歡用拷貝賦值函數(shù)來實現(xiàn)你的拷貝構(gòu)造函數(shù)該怎樣做呢,那你也可能試圖用 move 拷貝賦值函數(shù)來實現(xiàn) move 構(gòu)造函數(shù)。這樣作是可以的,但是你得小心。下面就是一個錯誤的實現(xiàn):
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.
(編譯器在這里進行了返回值優(yōu)化(RVO),但不是具名返回值優(yōu)化(NRVO)。就像我之前提到的,有些拷貝構(gòu)造函數(shù)被 RVO 或 NRVO 優(yōu)化掉了,但編譯器并不總是能夠做這樣的優(yōu)化,這時剩余的就由 move 構(gòu)造函數(shù)來優(yōu)化。)
move 構(gòu)造函數(shù)中標記為 WRONG 的那一行,調(diào)用了拷貝賦值函數(shù),編譯能通過也能運行,但這違背了 move 構(gòu)造函數(shù)的本意。(譯注:因為那個拷貝賦值函數(shù)只是進行普通的拷貝賦值,而不是 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 的話,那么對它施加的第一個操作能夠“竊取”它,而后續(xù)操作就沒機會了。這里的“竊取”是說不會被察覺到,所以這是行不通的。另一方面,不具名 rvalue 引用不能被重復使用,所以它仍保持右值(rvalueness)語意。
如果你真的打算用 move 賦值函數(shù)來實現(xiàn) move 構(gòu)造函數(shù),你需要從 lvalue move,就像是從 rvalue move 一樣。C++0x <utility> 中的 std::move() 具備這樣的能力,VC10將會有這個(實際上,開發(fā)版中已經(jīng)有了),但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(),因為它們的實現(xiàn)是等價的) std::move() 是怎樣工作的呢?目前,我只能跟你說這是“魔法”。(后面會有完整的解釋,并不復雜,但它與模板參數(shù)推導和引用折疊(reference collapsing,譯注:引用的引用)有 關(guān),后面講完美轉(zhuǎn)發(fā)的時候我們還會遇到這兩個東西)。我可以用一個具體的例子來略過“魔法”:給定一個 string 類型的左值,像前面重載決議例子中的 up ,std::move(up) 調(diào)用 string&& std::move(string&),這個函數(shù)返回一個不具名的 rvalue 引用,它是一個 rvalue。給定一個 string 類型的 rvalue,像前面重載決議例子中的 strange(), std::move(strange()) 調(diào)用 string&& std::move(string&&),同樣這個函數(shù)還是返回一個不具名的 rvalue,還是 rvalue。
std::move() 除了讓你能用 move 復制函數(shù)來實現(xiàn) move 構(gòu)造函數(shù)之外,還能在其他地方發(fā)揮作用。無論何時,只要你有一個左值,而它的值也不再重要了(例如,它將被銷毀或被賦值),你就可以使用 std::move(你的左值表達式) 來使用 move 語意。
move 語意:可移動成員(movable member)
C++0x 的標準類型(像 vector, string, regex) 都有 move 構(gòu)造函數(shù)和 move 賦值函數(shù)。而且我們也已經(jīng)看到了如何在我們自己的類中通過手動管理資源來實現(xiàn) move 語意(像前面的 remote_integer 類)。如果類中包含可移動數(shù)據(jù)成員(像 vector, string, regex, remote_integer )時該怎么辦呢?編譯器不會自動幫我們自動產(chǎn)生 move 構(gòu)造函數(shù)和 move 賦值函數(shù),所以我們必須手動編寫它們。很幸運,有了 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.
現(xiàn)在你看到啦,按成員移動(memberwise move)是很容易做到的。注意, remote_point 的 move 賦值函數(shù)沒有進行自我賦值檢查,是因為 remote_integer 已經(jīng)檢查過了。也要注意到 remote_point 隱式聲明的拷貝構(gòu)造函數(shù),拷貝賦值函數(shù)和析構(gòu)函數(shù)都正常運作。
到現(xiàn)在,你應該對 move 語意已經(jīng)非常熟悉了。(希望不是抓狂啊!)為了測試你新獲得的這個不可思議的技能,請為前面的例子寫一個 +() 操作符函數(shù)當作練習吧。
最后的提醒:只要你的類支持 move 語意,你就應該實現(xiàn) move 構(gòu)造函數(shù)和 move 賦值函數(shù)。因為不僅僅是你平常使用這些類時可從 move 語意中獲利, STL 容器和算法也能從中獲利,通過廉價的 move 省下昂貴的拷貝開銷。
(轉(zhuǎn)載請注明出處,作者與譯者信息,請勿用于商業(yè)用途)
< 前一頁 后一頁 >