基本概念:
左值:在內存中有可以訪問的地址,對象是一個左值蹋艺。
右值:不可以取地址,整數10是個右值黄刚。
引用:對象的別名捎谨,沒有創(chuàng)建新的對象,僅僅給已經存在的對象賦予了一個新的名字憔维。
引用是對象的別名涛救,對于引用的一切操作都是對對象的操作;
引用自身從概念上沒有大幸蛋恰(或者就是對象的大屑爝骸);但引用在傳遞或需要存儲時凶赁,其傳遞或存儲的大小為地址的大小咧栗。
引用必須初始化;
引用不可能重新綁定虱肄;
將指針所指向的對象綁定到一個引用時致板,需要確保指針非空。
任何引用類型的變量咏窿,都是左值斟或。
四種類型引用:
類型 | 例子 | 備注 |
---|---|---|
const lvalue refrence |
Foo foo(10); const Foo& ref = foo; | |
const rvalue refrence |
const Foo& ref = Foo(10); | |
non-const lvalue refrence |
Foo foo(10); Foo& ref = foo; | |
non-const rvalue refrence |
Foo&& ref=Foo(10); | C++11才開始有 |
move語義:
C++11 之前,只有 copy 語意集嵌,這對于極度關注性能的語言而言是一個重大的缺失萝挤。
對move 語意的急迫需求御毅,到了 C++11 終于被引入。其直接的驅動力很簡單:在構造或者賦值時怜珍,如果等號右側是一個中間臨時對象端蛆,應直接將其占用的資源直接 move 過來(對方就沒有了)。
但問題是酥泛,如何讓一個構造函數今豆,或者賦值操作重載函數能夠識別出來這是一個臨時變量?
/////////////hello.cpp/////////////////
#include <iostream>
using namespace std;
struct Foo
{
Foo(){ cout << "Foo()" << endl; }
Foo(const Foo&ref){ cout << "Foo(const Foo&)" << endl; } // copy ctor
Foo(Foo&& ref){ cout << "Foo(Foo&&)" << endl; } // move ctor
Foo& operator=(const Foo& rhs){cout << "Foo& operator=(const Foo& rhs)" << endl; } // copy assignment
Foo& operator=(Foo&& rhs){cout << "Foo& operator=(Foo&& rhs)" << endl; } // move assignment
~Foo(){ cout << "~Foo()" << endl; }
};
int main(int argc, char* argv[])
{
cout<<"=========="<<endl;
Foo foo1 = Foo();
cout<<"=========="<<endl;
foo1 = Foo();
cout<<"=========="<<endl;
Foo foo2 = foo1;
cout<<"=========="<<endl;
foo2 = foo1;
getchar();
return 1;
}
實參類型為non-const lvalue reference柔袁、const lvalue reference呆躲、 const rvalue reference可以匹配到copy ctor
和copy assignment
。
實參類型為non-const rvalue reference 才能匹配到 move ctor
和 move assignment
捶索。
通過這樣的方式插掂,讓 Foo foo1 = Foo()
或 foo1 = Foo()
這樣的表達式,都可以匹配到 move
語意的版本腥例。
與此同時辅甥,讓 Foo foo2 = foo1
或 foo2 = foo1
這樣的表達式,依然使用 copy 語意的版本院崇。
達到以上效果需要編譯時加上
-fno-elide-constructors
肆氓,以此關閉編譯器省略創(chuàng)建一個只是為了初始化另一個同類型對象的臨時對象
的優(yōu)化。root@ubuntu-Vostro-3268:/mnt/zpp# g++ hello.cpp -fno-elide-constructors root@ubuntu-Vostro-3268:/mnt/zpp# root@ubuntu-Vostro-3268:/mnt/zpp# root@ubuntu-Vostro-3268:/mnt/zpp# ./a.out ========== Foo() Foo(Foo&&) ~Foo() ========== Foo() Foo& operator=(Foo&& rhs) ~Foo() ========== Foo(const Foo&) ========== Foo& operator=(const Foo& rhs)
使用編譯器優(yōu)化時:
root@ubuntu-Vostro-3268:/mnt/zpp# g++ hello.cpp root@ubuntu-Vostro-3268:/mnt/zpp# ./a.out ========== Foo() ========== Foo() Foo& operator=(Foo&& rhs) ~Foo() ========== Foo(const Foo&) ========== Foo& operator=(const Foo& rhs)
練習:
struct Foo
{
Foo(int a) :a(a){}
int a;
};
void test1(Foo&& f)
{
// 對于任何類型為 右值引用的變量(當然也包括函數參數)底瓣,只能由右值來初始化谢揪;
}
void test2(Foo& f)
{
// 一個右值,不能被 T& 類型的參數匹配捐凭;畢竟這種可以修改的承諾拨扶。而修改一個調用后即消失的臨時
// 對象上,沒有任何意義茁肠,反而會導致程序員犯下潛在的錯誤患民,因而還是禁止了最好
}
void test3(const Foo& f)
{
}
Foo f1(1);
test1(f1); // wrong cannot bind ‘Foo’ lvalue to ‘Foo&&’ 不能將一個左值綁定到右值引用
test2(f1); // ok
test3(f1); // ok
test1(Foo{1}); // ok Foo{1}是右值
test2(Foo{1}); // wrong 這種做法無意義,invalid initialization of non-const reference of type ‘Foo&’ from an rvalue of type ‘Foo’
test3(Foo{ 1 }); // ok
// ref是左值雖然其類型是右值引用;
// 一個類型為 右值引用的變量,一旦被初始化之后垦梆,臨時對象的生命將被擴展匹颤,會在其被創(chuàng)建的 scope 內始終有效。
// 看似 ref 被定義的類型為 右值引用托猩,但這僅僅約束它的初始化:只能從一個 右值進行初始化印蓖。
// 但一旦初始化完成,它就和一個 左值引用再也沒有任何差別:都是一個已存在對象的 標識京腥。
Foo&& ref = Foo{1};
test1(ref); // wrong ref是左值,test1的形參為右值引用,右值引用的變量只能由右值來初始化 cannot bind ‘Foo’ lvalue to ‘Foo&&’
test2(ref); // ok
test3(ref); // ok
速亡值:
只有右值臨時對象可以初始化右值引用變量赦肃,從而也只有右值臨時變量能夠匹配參數類型為 右值引用(T&&)的函數,包括 move 構造函數。
如果程序員就是想把一個左值 move 給另外一個對象他宛,該怎么辦船侧?最簡單的選擇是通過 static_cast 進行類型轉換:
Foo foo{10}; // foo為左值
Foo&& ref = Foo{10}; // ref為左值 類型為右值引用
Foo obj1 = static_cast<Foo&&>(foo); // move 構 造
Foo obj2 = static_cast<Foo&&>(ref); // move 構 造
我們之前說過,只有 右值厅各,才可以用來初始化一個 右值引用類型的變量镜撩,因而也只有 右值才能匹配 move構造。
所以队塘,static_cast<Foo&&>(foo)
表達式琐鲁,肯定是一個 右值。
但同時人灼,它返回的類型又非常明確的是一個 引用,而這一點又不符合 右值的定義顾翼。
因為投放,所有的右值,都必須是一個 具體類型适贸,不能是不完備類型灸芳,也不能是抽象類型,但 引用拜姿,無論左值引用烙样,還是右值引用,都可以是不完備類型的引用或抽象類型的引用蕊肥。這是 左值才有的特征谒获。
對于這種既有左值特征,又和右值臨時對象一樣壁却,可以用來初始化右值引用類型的變量的表達式批狱,只能將其歸為新的類別。C++11 給這個新類別命名為 速亡值 (eXpiring value展东,簡稱 xvalue)赔硫。
而將原來的 右值,重新命名為 純右值盐肃。而 速亡值和 純右值合在一起爪膊,稱為 右值,其代表的含義是砸王,所有可以直接用來初始化 右值引用類型變量的表達式推盛。
同時,由于 速亡值又具備左值特征:可以是不完備類型处硬,可以是抽象類型小槐,可以進行運行時多態(tài)。所以,速亡值又和 左值一起被歸類為 泛左值(generalized lvalue, 簡稱 glvalue)凿跳。
? 類型為 右值引用的變量件豌,只能由 右值表達式初始化;
? 右值包括 純右值和 速亡值控嗜,其中 速亡值的類型是 右值引用茧彤;
? 類型為 右值引用的變量,是一個 左值疆栏,因而不能賦值給其它類型為 右值引用的變量曾掂,當然也不能匹配參數類型為 右值引用的函數。