右值、移動語義和完美轉發
1. lvalue / rvalue
An lvalue is an expression that refers to a memory location and allows us to take the address of that memory location via the & operator. An rvalue is an expression that is not an lvalue.
即,存在於記憶體且可用&
取地址的表示式為右值,否則為左值。在Value categories 詳細的解釋瞭如何區分左值和右值。
1.1 lvalue
int i = 42;
i = 43; // ok, i is an lvalue
int* p = &i; // ok, i is an lvalue
int& foo();
foo() = 42; // ok, foo() is an lvalue
int* p1 = &foo(); // ok, foo() is an lvalue
1.2 rvalue
int foobar();
int j = 0;
j = foobar(); // ok, foobar() is an rvalue
int* p2 = &foobar(); // error, cannot take the address of an rvalue
j = 42; // ok, 42 is an rvalue
2. lvalue reference / rvalue reference
lvalue reference: T&
rvalue reference: T&&
3. Rvalue reference
右值引用的兩大意義
- move semantics
- perfect forwarding
4. Universal references VS Rvalue references
4.1 形式
同為 T&&
4.2 Universal references
可左值可右值,和右值引用的形式相同,一般在以下兩種情況出現
- 函式模板:
template <typename T> void f(T&&);
- auto 宣告:
auto&& var = var1
4.2.1 reference collapsing
通用引用的型別推導規格,即為reference collapsing,如下
- A& & becomes A&
- A& && becomes A&
- A&& & becomes A&
- A&& && becomes A&&
4.3 Rvalue references
只能繫結到右值。除去Universal references就是右值引用了。
5. move semantics
移動語義,主要解決物件資源所有權轉移的問題。在C++11之前,由於移動語義的缺失造成的資源轉移需要通過構造(複製)/析構來實現是影響C++效能的一個重要原因。
5.1 std::move
std::move
無條件將型別轉換為右值。其作用是為了強制實現移動語義。
// vs 2017 : move
template<class _Ty>
_NODISCARD constexpr remove_reference_t<_Ty>&& move(_Ty&& _Arg) _NOEXCEPT
{ // forward _Arg as movable
return (static_cast<remove_reference_t<_Ty>&&>(_Arg));
}
例如強制將左值轉換為右值
int a = 32; // lvalue
f(std::move(a)); // rvalue
5.2 perfect forwarding
問題引入,如何將一個函式的引數原封不動的傳給另外一個函式?std::forward
根據型別轉換為左值或右值,從而實現完美轉發。
// vs 2017 : forward
template<class _Ty>
_NODISCARD constexpr _Ty&& forward(remove_reference_t<_Ty>& _Arg) _NOEXCEPT
{ // forward an lvalue as either an lvalue or an rvalue
return (static_cast<_Ty&&>(_Arg));
}
template<class _Ty>
_NODISCARD constexpr _Ty&& forward(remove_reference_t<_Ty>&& _Arg) _NOEXCEPT
{ // forward an rvalue as an rvalue
static_assert(!is_lvalue_reference_v<_Ty>, "bad forward call");
return (static_cast<_Ty&&>(_Arg));
}
通常用在需要將繫結右值的左值轉換為右值的地方,比如,函式引數總為左值,但是其可能綁定了一個右值引用。
Named variables and parameters of rvalue reference type are lvalues.
例如:
int&& a = 32;
void func(int&& param) // 函式引數總為左值
{}
上邊a
和param
都是左值,但是他們都是繫結到了右值上。
如果有兩個過載函式
void f1(const int&);
void f1(int&&);
當通過f1(a)
呼叫時,都會呼叫void f1(const int&);
我們可以通過使用std::forward
來轉換
fw(std::forward<decltype(a)>(a));
其更常用在Universal references的函式中來呼叫其他函式,因為引數都是左值,所以需要用 std::forward
將其型別轉換為左值或右值;
template <typename T>
void f(T&& param)
{
f1(param); // always: void f1(const int&)
}
template <typename T>
void f(T&& param)
{
f1(std::forward<T>(param)); // depend on T
}