C++11中定義了右值引用的概念,這是一個比較實用,但不好理解的內容。
右值引用,顧名思義,就是引用右值。
那什麼是右值呢?書的給出的解釋是:可以取地址的,有名字的就是左值。反之,為右值。
T a(1);
T b(a);
其中 a是左值,b也是右值。
T ReturnRValue() {
return T(3);
}
T c = ReturnRValue()
上面這個函數調用返回過程中,首先 T(3) 生成了一個對象,然後將這個對象賦值給tmp對象,T tmp = T(3);在ReturnRValue()函數返回後,會有個 T c = tmp 的動作。這個過程中,tmp,T(3) 都是右值。
左值與右值比較,比較明顯的區別是:
右值是自動產生的,不被用戶直接操作的,沒有名稱的,將立即被銷毀的對象。
左值則是有名稱的,能夠被操作的對象。
int a; // a左值
int b = a + 1; // (a + 1)是右值,b是左值
那為什麼出來右值引用的概念呢?
因為我們用左值引用的方式對訪問右值,是不能對其進行修改的。如:
const T &d = ReturnRValue(); //! 通過編譯
T &d = ReturnRValue(); //! 編譯錯誤
error: invalid initialization of non-const reference of type 『T&』 from an rvalue of type 『T』
如果我們想要操作右值呢?那麼必須要用右值引用。
T &&d = ReturnRValue(); //! C++11上通過編譯
之後,可以像訪問普通變量一樣去訪問變量d。
右值引用讓我們在接收到右值參數的時候,可以操作去操作右值的內容。在複製構造與賦值過程中,如果可以操作右值,那麼可以將右值中的資源直接轉換到新的對象裡,從而減免了申請資源,再複製資源的操作過程。
左值構造:
T(const T &src) { //! 左值構造(複製)
cout << "T(const T &):" << this << "<--" << &src << endl;
_big_block = new char [1024];
memcpy(_big_block, src._big_block, BLOCK_SIZE);
}
右值構造:
T(T &&src) { //! 右值構造(移動)
cout << "T(T &&):" << this << "<--" << &src << endl;
_big_block = src._big_block;
src._big_block = NULL;
}
如下為整體的試驗代碼:
#include <iostream>
#include <cstring>
using namespace std;
#define BLOCK_SIZE 1024
struct T {
T() { //! 構造
cout << "T():" << this << endl;
_big_block = new char [BLOCK_SIZE];
}
T(const T &src) { //! 左值構造(複製)
cout << "T(const T &):" << this << "<--" << &src << endl;
_big_block = new char [1024];
memcpy(_big_block, src._big_block, BLOCK_SIZE);
}
~T() { //! 析構
delete [] _big_block;
cout << "~T():" << this << endl;
}
T& operator = (const T &src) { //! 左值賦值(複製)
cout << "operator=(const T&):" << this << "<--" << &src << endl;
if (_big_block != NULL)
delete [] _big_block;
_big_block = new char [BLOCK_SIZE];
memcpy(_big_block, src._big_block, BLOCK_SIZE);
}
T(T &&src) { //! 右值構造(移動)
cout << "T(T &&):" << this << "<--" << &src << endl;
_big_block = src._big_block;
src._big_block = NULL;
}
T& operator = (T &&src) { //! 右值賦值(移動)
cout << "operator=(T &&):" << this << "<--" << &src << endl;
if (_big_block != NULL)
delete [] _big_block;
_big_block = src._big_block;
src._big_block = NULL;
}
private:
char *_big_block;
};
/////////////////////////////////////////////////////
T ReturnRvalue() { //! 返回一個T對象
return T();
}
void AcceptT(const T &) { //! 接受左值引用
cout << "Accept(const T &)" << endl;
}
void AcceptT(T &&) { //! 接受右值引用
cout << "Accept(T &&)" << endl;
}
/////////////////////////////////////////////////////
int main() {
cout << "> T a" << endl;
T a;
cout << "> T b = a" << endl;
T b = a; //! 左值構造
cout << "> T c" << endl;
T c;
cout << "> c = a" << endl;
c = a; //! 左值賦值
cout << ">" << endl;
cout << ">" << endl;
cout << "> T d = ReturnRvalue()" << endl;
T d = ReturnRvalue(); //! 以右值構造d
cout << "> T && e = ReturnRvalue()" << endl;
T && e = ReturnRvalue(); //! 定義e引用右值,而e本身是左值
cout << "> T f = e" << endl;
T f = e; //! 以左值e構造f,e是左值
cout << "> d = ReturnRvalue()" << endl;
d = ReturnRvalue(); //! 右值賦值
cout << ">" << endl;
cout << ">" << endl;
cout << "> AcceptT(a)" << endl;
AcceptT(a); //! 接受左值
cout << "> AcceptT(ReturnRvalue())" << endl;
AcceptT(ReturnRvalue()); //! 接受右值
cout << ">" << endl;
return 0;
}
編譯命令:g++ -o test test.cpp -std=c++11 -fno-elide-constructors
運行結果:
> T aT():0x7ffd354d68c0> T b = aT(const T &):0x7ffd354d68b0<--0x7ffd354d68c0 --a是左值,所以採用左值複製構造函數> T cT():0x7ffd354d68a0> c = aoperator=(const T&):0x7ffd354d68a0<--0x7ffd354d68c0 --a是左值,所以採用左值賦值函數>>> T d = ReturnRvalue()T():0x7ffd354d6850 --創建T()對象T(T &&):0x7ffd354d68d0<--0x7ffd354d6850 --將T()移動到tmp對象,由於T()為右值,所以用右值移動構造函數~T():0x7ffd354d6850 --析構T()對象T(T &&):0x7ffd354d6890<--0x7ffd354d68d0 --將tmp移動到d對象,由於tmp為右值,所以用右值移動構造函數~T():0x7ffd354d68d0 --析構tmp對象> T && e = ReturnRvalue()T():0x7ffd354d6850 --創建T()對象T(T &&):0x7ffd354d68e0<--0x7ffd354d6850 --將T()移動到tmp對象,由於T()為右值,所以用右值移動構造函數~T():0x7ffd354d6850 --析構T()對象,e引用的是tmp對象> T f = eT(const T &):0x7ffd354d6880<--0x7ffd354d68e0 --e雖然代表的是tmp對象,但e會被繼續訪問,所以是左值,這裡採用的是左值複製構造函數> d = ReturnRvalue()T():0x7ffd354d6850T(T &&):0x7ffd354d68f0<--0x7ffd354d6850 --將tmp移動到d對象,由於tmp為右值,所以用右值移動賦值函數~T():0x7ffd354d6850operator=(T &&):0x7ffd354d6890<--0x7ffd354d68f0~T():0x7ffd354d68f0>>> AcceptT(a)Accept(const T &) --a是左值> AcceptT(ReturnRvalue())T():0x7ffd354d6850T(T &&):0x7ffd354d6900<--0x7ffd354d6850~T():0x7ffd354d6850Accept(T &&) --ReturnRValue()返回的是右值~T():0x7ffd354d6900>~T():0x7ffd354d6880 --左值對象析構,不解析~T():0x7ffd354d68e0~T():0x7ffd354d6890~T():0x7ffd354d68a0~T():0x7ffd354d68b0~T():0x7ffd354d68c0使用右值引用可以提升賦值效率,避免臨時變量賦值或構造過程中沒有必要的複製過程。