一. 左值和右值
左值
: 可以取地址的對象
右值
: 不可以取地址的對象
double x=1.0, y = 2.0;
1; // 字面量, 不可取地址, 是右值
x + y; // 表達式返回值, 不可取地址, 是右值
max(x, y); // 傳值返回函數的返回值 (非引用返回)
總結就是: 根據是否可以取地址來區分是左值還是右值
二.左值引用和右值引用
左值引用: 對左值的引用, 給左值起別名, 主要是為了避免對象拷貝
.
int a = 1;
int& la = a;
右值引用: 對右值的引用, 給右值起別名, 主要是為了延長對象的生命周期
int&& ra = 10;
注意
, 右值引用變量, 其實是左值
, 可以對它取地址和賦值
int x = 1, y = 2;
int&& right_ref = x + y;
cout << right_ref << endl;
right_ref = 10;
cout << right_ref << endl;
2.1 左值引用指向右值 和 右值引用指向左值
左值引用可以指向右值, 需要const來修飾
, 這一點在方法里很常用, 比如push_back接口, 并且const修是, 所以即使傳進來的是右值, 也沒關系.
void test(const int& a)
{cout << a << endl;
}int main() {int a = 1;int&& ra = move(a);test(a);test(ra);
}
右值引用可以指向左值, 需要std::move(v)
int a = 1;
int&& ra = move(a);
cout << ra << endl;
a = 2;
cout << ra << endl;
ra = 3;
cout << a << endl;
三. 左值引用的意義
在傳參
和返回值
時, 避免對象拷貝, 節省了內存, 提搞了效率
class A
{
public:int _a;A(int a) : _a(a) {};// 返回值時, 避免拷貝A& operator +=(const int& i){_a += i;return *this;}
};// 傳參時, 避免拷貝
void test(const A& a)
{cout << a._a << endl;
}int main() {A a(1);test(a);a += 1;test(a);
}
但是在返回值
返回左值引用時, 如果返回的是局部變量, 那么除了函數作用域是不行的
class A
{
public:int _a;A(int a) : _a(a) {};A& operator +=(const int& i){A a(_a);// 返回局部變量, 會直接報錯return *a;}
};int main() {A a(1);a += 1;
}
四. 右值引用的意義
將一個對象中的資源移動到另一個對象(資源控制權的轉移)。
拷貝構造
: const左值引用
移動構造
: 右值引用
移動賦值
: const左值引用, 是運算符重載
用例如下
class A
{
public:int _a;A(int a) : _a(a) {cout << "構造函數" << endl;};A(const A& a){cout << "拷貝構造函數" << endl;_a = a._a;}A(A&& a) noexcept{cout << "移動構造函數" << endl;_a = a._a;}A& operator=(const A& a) noexcept{cout << "移動賦值函數" << endl;_a = a._a;return *this;}
};A test()
{A a(1);cout << &a << endl;// 但是現代編譯器中, 這里已經不執行移動構造函數了, 用了更好的優化方式return a;
}int main() {A a = test();cout << &a << endl;A a2 = a;A a3 = std::move(a);A a4(1);a4 = std::move(a3);
}
執行結果
五. 完美轉發
5.1 前提知識
函數模板中的&&
不表示右值引用, 而表示萬能引用
不完美轉發的例子
void f(int& x)
{cout << "左值引用" << endl;
}void f(const int& x)
{cout << "const 左值引用" << endl;
}void f(int&& x)
{cout << "右值引用" << endl;
}void f(const int&& x)
{cout << "const右值引用" << endl;
}template<typename T>
void test(T&& t)
{f(t);
}int main() {int a = 1;test(a);const int b = 1;test(b);test(1);const int d = 1;test(std::move(d));
}
執行結果
和我們猜想的差別很大, 是因為, 右值引用
本身是左值, 所以在模板函數中, 我們傳進去的是右值, 但是等到調用f
函數的時候, 已經全部是左值了
5.2完美轉發
由上面的例子我們可以看到, 是一個不完美轉發
, 因為右值
失去了它的右值屬性
, 于是便提出了完美轉發
, 核心方法是
std::forward
, 它在傳參過程中, 保留對象原生類型的屬性, 我們稍加修改一下上面的例子
void f(int& x)
{cout << "左值引用" << endl;
}void f(const int& x)
{cout << "const 左值引用" << endl;
}void f(int&& x)
{cout << "右值引用" << endl;
}void f(const int&& x)
{cout << "const右值引用" << endl;
}template<typename T>
void test(T&& t)
{f(std::forward<T>(t));
}int main() {int a = 1;test(a);const int b = 1;test(b);test(1);const int d = 1;test(std::move(d));
}
執行結果
和我們預想的一致了
總結
右值引用是c++11引入的最重要的新特性之一, 配合著移動語義和完美轉發, 是的c++程序運行更加高效.