C++11 std::move和std::forward


下文先從C++11引入的幾個規則,如引用折疊、右值引用的特殊類型推斷規則、static_cast的擴展功能說起,然后通過例子解析std::move和std::forward的推導解析過程,說明std::move和std::forward本質就是一個轉換函數,std::move執行到右值的無條件轉換,std::forward執行到右值的有條件轉換,在參數都是右值時,二者就是等價的。其實std::move和std::forward就是在C++11基本規則之上封裝的語法糖。

1 引入的新規則

規則1(引用折疊規則):如果間接的創建一個引用的引用,則這些引用就會“折疊”。在所有情況下(除了一個例外),引用折疊成一個普通的左值引用類型。一種特殊情況下,引用會折疊成右值引用,即右值引用的右值引用, T&& &&。即

  • X& &、X& &&、X&& &都折疊成X&
  • X&& &&折疊為X&&

規則2(右值引用的特殊類型推斷規則):當將一個左值傳遞給一個參數是右值引用的函數,且此右值引用指向模板類型參數(T&&)時,編譯器推斷模板參數類型為實參的左值引用,如

template<typename T> 
void f(T&&);

int i = 42;
f(i)

 

上述的模板參數類型T將推斷為int&類型,而非int。

若將規則1和規則2結合起來,則意味着可以傳遞一個左值int i給f,編譯器將推斷出T的類型為int&。再根據引用折疊規則 void f(int& &&)將推斷為void f(int&),因此,f將被實例化為: void f<int&>(int&)。

從上述兩個規則可以得出結論:如果一個函數形參是一個指向模板類型的右值引用,則該參數可以被綁定到一個左值上,即類似下面的定義:

template<typename T> void f(T&&); 

規則3:雖然不能隱式的將一個左值轉換為右值引用,但是可以通過static_cast顯示地將一個左值轉換為一個右值。【C++11中為static_cast新增的轉換功能】。

2 std::move

2.1 std::move的使用

class Foo
{
public:
    std::string member;

    // Copy member.
    Foo(const std::string& m): member(m) {}

    // Move member.
    Foo(std::string&& m): member(std::move(m)) {}
};

 

上述Foo(std::string&& member)中的member是rvalue reference,但是member卻是一個左值lvalue,因此在初始化列表中需要使用std::move將其轉換成rvalue。

2.2 std::move()解析

標准庫中move的定義如下:

template<typename T> typename remove_reference<T>::type && move(T&& t) { return static_cast<typename remove_reference<T>::type &&>(t); } 
  • move函數的參數T&&是一個指向模板類型參數的右值引用【規則2】,通過引用折疊,此參數可以和任何類型的實參匹配,因此move既可以傳遞一個左值,也可以傳遞一個右值;
  • std::move(string("hello"))調用解析:
    • 首先,根據模板推斷規則,確地T的類型為string;
    • typename remove_reference<T>::type && 的結果為 string &&;
    • move函數的參數類型為string&&;
    • static_cast<string &&>(t),t已經是string&&,於是類型轉換什么都不做,返回string &&;
  • string s1("hello"); std::move(s1); 調用解析:
    • 首先,根據模板推斷規則,確定T的類型為string&;
    • typename remove_reference<T>::type && 的結果為 string&
    • move函數的參數類型為string& &&,引用折疊之后為string&;
    • static_cast<string &&>(t),t是string&,經過static_cast之后轉換為string&&, 返回string &&;

從move的定義可以看出,move自身除了做一些參數的推斷之外,返回右值引用本質上還是靠static_cast<T&&>完成的。

因此下面兩個調用是等價的,std::move就是個語法糖。

void func(int&& a)
{
    cout << a << endl;
}

int a = 6;
func(std::move(a));

int b = 10;
func(static_cast<int&&>(b)); 

 

std::move執行到右值的無條件轉換。就其本身而言,它沒有move任何東西。

3 std::forward()

3.1 完美轉發

完美轉發實現了參數在傳遞過程中保持其值屬性的功能,即若是左值,則傳遞之后仍然是左值,若是右值,則傳遞之后仍然是右值。

C++11 lets us perform perfect forwarding, which means that we can forward the parameters passed to a function template to another function call inside it without losing their own qualifiers (const-ref, ref, value, rvalue, etc.).

3.2 std::forward()解析

std::forward只有在它的參數綁定到一個右值上的時候,它才轉換它的參數到一個右值。

class Foo
{
public:
    std::string member;

    template<typename T>
    Foo(T&& member): member{std::forward<T>(member)} {}
};

 

傳遞一個lvalue或者傳遞一個const lvaue

  • 傳遞一個lvalue,模板推導之后 T = std::string&
  • 傳遞一個const lvaue, 模板推導之后T = const std::string&
  • T& &&將折疊為T&,即std::string& && 折疊為 std::string&
  • 最終函數為: Foo(string& member): member{std::forward<string&>(member)} {}
  • std::forward<string&>(member)將返回一個左值,最終調用拷貝構造函數

傳遞一個rvalue

  • 傳遞一個rvalue,模板推導之后 T = std::string
  • 最終函數為: Foo(string&& member): member{std::forward<string>(member)} {}
  • std::forward<string>(member) 將返回一個右值,最終調用移動構造函數;

std::move和std::forward本質都是轉換。std::move執行到右值的無條件轉換。std::forward只有在它的參數綁定到一個右值上的時候,才轉換它的參數到一個右值。

std::move沒有move任何東西,std::forward沒有轉發任何東西。在運行期,它們沒有做任何事情。它們沒有產生需要執行的代碼,一byte都沒有。

4 std::move()和std::forward()對比

  • std::move執行到右值的無條件轉換。就其本身而言,它沒有move任何東西。
  • std::forward只有在它的參數綁定到一個右值上的時候,它才轉換它的參數到一個右值。
  • std::move和std::forward只不過就是執行類型轉換的兩個函數;std::move沒有move任何東西,std::forward沒有轉發任何東西。在運行期,它們沒有做任何事情。它們沒有產生需要執行的代碼,一byte都沒有。
  • std::forward<T>()不僅可以保持左值或者右值不變,同時還可以保持const、Lreference、Rreference、validate等屬性不變;

5 一個完整的例子

#include <iostream>
#include <type_traits>
#include <typeinfo>
#include <memory>
using namespace std;

struct A
{
    A(int&& n)
    {
        cout << "rvalue overload, n=" << n << endl;
    }
    A(int& n)
    {
        cout << "lvalue overload, n=" << n << endl;
    }
};

class B
{
public:
    template<class T1, class T2, class T3>
    B(T1 && t1, T2 && t2, T3 && t3) :
        a1_(std::forward<T1>(t1)),
        a2_(std::forward<T2>(t2)),
        a3_(std::forward<T3>(t3))
    {

    }
private:
    A a1_, a2_, a3_;
};

template <class T, class U>
std::unique_ptr<T> make_unique1(U&& u)
{
    //return std::unique_ptr<T>(new T(std::forward<U>(u)));
    return std::unique_ptr<T>(new T(std::move(u)));
}

template <class T, class... U>
std::unique_ptr<T> make_unique(U&&... u)
{
    //return std::unique_ptr<T>(new T(std::forward<U>(u)...));
    return std::unique_ptr<T>(new T(std::move(u)...));
}

int main()
{
    auto p1 = make_unique1<A>(2);

    int i = 10;
    auto p2 = make_unique1<A>(i);

    int j = 100;
    auto p3 = make_unique<B>(i, 2, j);
    return 0;
}

 

georgeguo

 

#include <iostream> // std::cout
#include <type_traits> // std::is_same
 
template<class T1, class T2>
void print_is_same() {
  std::cout << std::is_same<T1, T2>() << '\n';
}
 
int main() {
  std::cout << std::boolalpha;
 
  print_is_same<int, int>();
  print_is_same<int, int &>();
  print_is_same<int, int &&>();
 
  print_is_same<int, std::remove_reference<int>::type>();
  print_is_same<int, std::remove_reference<int &>::type>();
  print_is_same<int, std::remove_reference<int &&>::type>();
}

 


注意!

本站转载的文章为个人学习借鉴使用,本站对版权不负任何法律责任。如果侵犯了您的隐私权益,请联系我们删除。



 
粤ICP备14056181号  © 2014-2020 ITdaan.com