C++11: rvalue 右值
rvalue & lvalue(右值與左值)
左值與右值的定義是比較復(fù)雜蚪腋,下邊僅給出一個(gè)相對(duì)簡(jiǎn)單的定義,也是大家相對(duì)容易接受的一個(gè)定義吻商。
左值:能在內(nèi)存中取到地址的對(duì)象畔咧。
右值:不是左值的對(duì)象某抓。
左值舉例說(shuō)明一下:
int i; // i 是一個(gè)左值
int* p = &i; //獲取i的地址,且它的地址是唯一的
i = 2; //因?yàn)榭梢垣@取到它的地址疮蹦,所以可以進(jìn)行賦值操作
class Dog; //這是一個(gè)類(lèi)類(lèi)型
dog d1; //聲明了一個(gè)左值
在C++程序中诸迟,大部分的對(duì)象都是左值。
下面是右值的一些例子:
int x = 2; // 2 是右值
int x = i+2; //(i+2) 的結(jié)果是一個(gè)臨時(shí)變量,也是一個(gè)右值
int* l = &(i+2); //錯(cuò)誤 ??
i+2 = 4; //錯(cuò)誤 ??
2 = i; //錯(cuò)誤 ??
dog d1;
d1 = Dog(); //Dog()會(huì)產(chǎn)生一個(gè)臨時(shí)變量阵苇,所以它也是一個(gè)右值
int sum (int x, int y)
{
return x+y;
}
int i = sum(3, 4); //sum(3,4) 是一個(gè)右值
接下來(lái)我們來(lái)看一下左值引用和右值引用
先來(lái)看一下左值引用:
int i;
int& r = i; // r 是對(duì)左值 i 的引用壁公,這是對(duì)左值的引用
int& r = 5; //錯(cuò)誤 ??,無(wú)法對(duì)一個(gè)右值進(jìn)行左值引用操作绅项,但是有一種意外情況
const int& r = 5; //常量左值引用可以引用右值
int square(int& x)
{
return x * x;
}
square(i); //正確 ??
square(40); //錯(cuò)誤 ??
//如果希望square(40)正確調(diào)用的話(huà)紊册,可以使用上文的例外
int square(const int& x)
{
return x * x;
}
square(40) //正確 ??
接下來(lái)是右值引用,這里只是一個(gè)簡(jiǎn)單的語(yǔ)法上的例子快耿,下面有更詳細(xì)的例子
int a = 2;
int&& b = a + 2; //b 擴(kuò)展了臨時(shí)變量 a + 2 的生命周期
int&& c = 5;
左值可以用來(lái)構(gòu)建右值
int i = 1;
int x = i + 2; //i 是左值囊陡, i+2是右值
右值可以用來(lái)構(gòu)建左值
int v[3]; //3 是右值,v是左值
*(ptr+2) = 4; //4與2 是右值掀亥,ptr是左值
三個(gè)常見(jiàn)的誤解:
- 誤解一:函數(shù)和操作符的返回值為右值撞反。
之所以這么認(rèn)為是因?yàn)椋瘮?shù)和操作符經(jīng)常會(huì)返回一些臨時(shí)變量搪花,而臨時(shí)變量是右值
int x = i + 3; //i+3 返回了一個(gè)右值遏片,然后這個(gè)右值被賦值給了一個(gè)左值
int y = sum(3,4); //sum(3,4) 也返回了一個(gè)右值
int myglobal;
int& foo()
{
return myglobal; //該函數(shù)返回了一個(gè)左值引用
}
foo() = 50;
array[3] = 50; //[]操作符可以用來(lái)給元素賦值,其返回值也是左值引用
- 誤解二:左值是可以被修改的撮竿。
一個(gè)反例就是在左值前面加上const
修飾
const int c = 1;
c = 2;
- 誤解三:右值是不能被修改的吮便。
i+3 = 6; //右值無(wú)法被修改
sum(3,4) = 7; //右值無(wú)法被修改
class Dog
{
public:
Dog(){ state = 0;}
void sleep(){state = 2;}
private:
int state;
};
int main()
{
Dog().sleep(); //Dog() 是一個(gè)右值,但是sleep會(huì)改變它的成員變量的值
return 0;
}
每一個(gè)C++表達(dá)式幢踏,或者是一個(gè)左值髓需,或者是一個(gè)右值。如果一個(gè)對(duì)象有明確的可分辨的地址的話(huà)惑折,那它就是一個(gè)左值授账,反之是一個(gè)右值。
移動(dòng)語(yǔ)義
首先我們來(lái)看一下四個(gè)相互重載的函數(shù)惨驶,也就是白热,函數(shù)為左值引用或者右值引用是可以重載的。
// 1
void printInt(int& i) {std::cout<<"lvalue reference : "<<i<<endl;}
// 2
void printInt(int&& i) {std::cout<<"rvalue reference : "<<i<<endl;}
// 3
void printInt(const int& i) {std::cout<<"lvalue reference : "<<i<<endl;}
// 4
void printInt(int i) {std::cout<<"lvalue reference : "<<i<<endl;}
int main()
{
int a = 5;
printInt(a); //調(diào)用 1
printInt(6); //發(fā)生沖突粗卜,理論上2屋确、3、4都可以
return 0;
}
class boVector
{
private:
int size;
double* arr_; //會(huì)是一個(gè)特別大的數(shù)組
public:
boVector(const boVector& rhs) //深拷貝
{
size = rhs.size;
arr_ = new double[size];
for(int i = 0; i<size; i++){ arr_[i] = rhs.arr_[i];}
}
boVector(const boVector&& rhs) //淺拷貝
{
size = rhs.size;
arr_ =rhs.arr_];
rhs.arr_=nullptr;
}
~boVector(){delete arr_;}
};
void foo(boVector v);
boVector createBoVector();
int main()
{
boVector reuseable = createBoVector();
//這里會(huì)調(diào)用拷貝構(gòu)造函數(shù)续扔,會(huì)構(gòu)造這個(gè)比較大的數(shù)組數(shù)據(jù)成員攻臀,
//這將會(huì)是一個(gè)比較話(huà)費(fèi)時(shí)間的操作
foo(resuable);
//createBoVector() 會(huì)返回一個(gè)臨時(shí)變量作為參數(shù)傳遞給foo,
//這里會(huì)調(diào)用移動(dòng)拷貝構(gòu)造函數(shù)纱昧,不會(huì)重新創(chuàng)建比較大的數(shù)組
foo(createBoVector());
//如果resuable不再使用刨啸,我們也可以這么調(diào)用
foo(std::move(reusable));
//此時(shí) reusable.arr_ = nullptr
return 0;
}
Note:
(1) 右值引用最有用的地方是給類(lèi)添加移動(dòng)拷貝構(gòu)造函數(shù)和移動(dòng)賦值操作符函數(shù)。
(2) C++11的STL容易都已經(jīng)實(shí)現(xiàn)了移動(dòng)語(yǔ)義识脆,理論上设联,只要你在編譯代碼的時(shí)候添加-std=c++11
選項(xiàng)善已,就能獲得性能上的提升。
完美轉(zhuǎn)發(fā) prefect forwarding
template<typename T>
void relay(T arg)
{
foo(T);
}
int main()
{
boVector reusable = createBoVector();
relay(reusable);
relay(createBoVector());
}
這就做參數(shù)轉(zhuǎn)發(fā)
理想的情況下离例,relay的參數(shù)怎么轉(zhuǎn)發(fā)給foo函數(shù)呢换团,應(yīng)該要滿(mǎn)足兩個(gè)條件:
- 沒(méi)有花費(fèi)較大或者不必要的拷貝構(gòu)造函數(shù)的調(diào)用
- 左值需要被轉(zhuǎn)發(fā)為左值,右值需要被轉(zhuǎn)發(fā)為右值
滿(mǎn)足上述兩點(diǎn)稱(chēng)之為完美轉(zhuǎn)發(fā)
標(biāo)準(zhǔn)庫(kù)已經(jīng)幫我們提供了一個(gè)函數(shù)std::forward
來(lái)進(jìn)行完美轉(zhuǎn)發(fā)宫蛆,該函數(shù)位于<utility>
中艘包。利用該函數(shù)將relay
改為
template<typename T>
void relay(T&& arg)
{
foo(std::forward<T>(arg));
}
下面,我們?cè)谘a(bǔ)充一下完美轉(zhuǎn)發(fā)的一些細(xì)節(jié)耀盗。
首先來(lái)講一個(gè)概念想虎,引用坍縮(reference collapse),我覺(jué)得中文的翻譯怪怪的袍冷,還是英文比較好理解磷醋,其實(shí)就是有多個(gè)&
的時(shí)候我們?cè)趺刺幚怼?/p>
typedef int& lref;
typedef int&& rref;
int n;
lref& r1 = n; // r1 的類(lèi)型是 int&
lref&& r2 = n; // r2 的類(lèi)型是 int&
rref& r3 = n; // r3 的類(lèi)型是 int&
rref&& r4 = 1; // r4 的類(lèi)型是 int&&
根據(jù)引用坍縮,我們可以得到
//T&& 被一個(gè)右值初始化
relay(9); => T = int&& => T&& int&& && = int&&
//T&& 被一個(gè)左值初始化
relay(x); => int& => T&& = int& && = int&
這里的T&&
不再是右值引用胡诗,而是universal reference
T&&
是Universial Reference
的條件:
- T 是一個(gè)模板類(lèi)型邓线。
- 類(lèi)型推倒是施加于T的,T是一個(gè)函數(shù)模板參數(shù)煌恢,而不是類(lèi)模板參數(shù)骇陈。
然后標(biāo)注庫(kù)還提供了一個(gè)類(lèi)型工具remove_reference
template<class T>
struct remove_reference;
remove_reference<int&>::type i; // int i
remove_reference<int>::type i; // int i
template < typename T >
void relay(T&& arg)
{
foo(std::forward<T>(arg));
}
template < class T >
T&& forward(typename remove_reference<T>::type& arg)
{
return static_cast<T&&>(arg);
}