1. 程式人生 > >右值、移動語義和完美轉發

右值、移動語義和完美轉發

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) // 函式引數總為左值
{}

上邊aparam都是左值,但是他們都是繫結到了右值上。
如果有兩個過載函式

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
}

references