右值引用 std::forward與完美轉發

來源:
https://www.cnblogs.com/5iedu/p/7742589.html

  • 1. std::forward原型

template <typename T>
T&& forward(typename std::remove_reference<T>::type& param) //左值引用版本
{
    return static_cast<T&&>(param);
}

template <typename T>
T&& forward(typename std::remove_reference<T>::type&& param)  //右值引用版本
{
    //param被右值初始化時,T應爲右值引用類型,如果T被綁定爲左值引用則報錯。
    static_assert(!std::is_lvalue_reference<_Tp>::value, "template argument"  
    " substituting _Tp is an lvalue reference type"); 
    
    return static_cast<T&&>(param);
}

//其中remove_reference的實現如下
//1. 特化版本(一般的類)
template <typename T>
struct remove_reference 
{
    typedef T type;
};

//2. 左值引用版本
template <typename T>
struct remove_reference<T&>
{
    typedef T type;
};

//3. 右值引用版本
template <typename T>
struct remove_reference<T&&>
{
    typedef T type;
};
  • 2. 完美轉發(Perfect Forwarding)

(1)完美轉發:是指在函數模板中,完全依照模板的參數類型(即保持實參的左值、右值特性),將實參傳遞給函數模板中調用的另外一個函數。

(2)原理分析

class Widget{};

//完美轉發
template<typename T>
void func(T&& fparam) //fparam是個Universal引用
{
    doSomething(std::forward<T>(fparam));
}

//1. 假設傳入func是一個左值的Widget對象, T被推導爲Widget&,則forward如下:
Widget& && forward(typename std::remove_reference<Widget&>::type& param)
{
    return static_cast<Widget& &&>(param);
}
//==>引用摺疊折後
Widget& forward(Widget& param)
{
    return static_cast<Widget&>(param);
}

//2. 假設傳入func是一個右值的Widget對象, T被推導爲Wiget,則forward如下:
Widget&& forward(typename std::remove_reference<Widget>::type& param)
{
    return static_cast<Widget&&>(param);
}

(3)std::forward和std::move的聯繫和區別

①std::move是無條件轉換,不管它的參數是左值還是右值,都會被強制轉換成右值。就其本身而言,它沒有move任何東西。

②std::forward是有條件轉換。只有在它的參數綁定到一個右值時,它才轉換它的參數到一個右值。當參數綁定到左值時,轉換後仍爲左值。

③對右值引用使用std::move,對universal引用則使用std::forward

④如果局部變量有資格進行RVO優化,不要把std::move或std::forward用在這些局部變量中

⑤std::move和std::forward在運行期都沒有做任何事情。

【編程實驗】不完美轉發和完美轉發

#include <iostream>
//#include <utility> //for std::forward
using namespace std;

void print(const int& t)
{
    cout <<"lvalue" << endl;
}

void print(int&& t)
{
    cout <<"rvalue" << endl;
}

template<typename T>
void Test(T&& v) //v是Universal引用
{
    //不完美轉發
    print(v);  //v具有變量,本身是左值,調用print(int& t)
    
    //完美轉發
    print(std::forward<T>(v)); //按v被初始化時的類型轉發(左值或右值)
    
    //強制將v轉爲右值
    print(std::move(v)); //將v強制轉爲右值,調用print(int&& t)
}

int main()
{
    cout <<"========Test(1)========" << endl; 
    Test(1); //傳入右值
    
    int x = 1;
    cout <<"========Test(x)========" << endl;
    Test(x); //傳入左值
    
    cout <<"=====Test(std::forward<int>(1)===" << endl;
    Test(std::forward<int>(1)); //T爲int,以右值方式轉發1
    //Test(std::forward<int&>(1)); //T爲int&,需轉入左值
    
    cout <<"=====Test(std::forward<int>(x))===" << endl;
    Test(std::forward<int>(x)); //T爲int,以右值方式轉發x
    cout <<"=====Test(std::forward<int&>(x))===" << endl;
    Test(std::forward<int&>(x)); //T爲int,以左值方式轉發x
    
    return 0;
}

輸出結果

e:\Study\C++11\16>g++ -std=c++11 test2.cpp
e:\Study\C++11\16>a.exe
========Test(1)========
lvalue
rvalue
rvalue
========Test(x)========
lvalue
lvalue
rvalue
=====Test(std::forward<int>(1)===
lvalue
rvalue
rvalue
=====Test(std::forward<int>(x))===
lvalue
rvalue
rvalue
=====Test(std::forward<int&>(x))===
lvalue
lvalue
rvalue

  • 3.萬能的函數包裝器

(1)利用std::forward和可變參數模板實現

①可將帶返回值、不帶返回值、帶參和不帶參的函數委託萬能的函數包裝器執行。

②Args&&爲Universal引用,因爲這裏的參數可能被左值或右值初始化。Funciont&&也爲Universal引用,如被lambda表達式初始化。

③利用std::forward將參數正確地(保持參數的左、右值屬性)轉發給原函數

【編程實驗】萬能的函數包裝器

#include <iostream>
using namespace std;

//萬能的函數包裝器
//可將帶返回值、不帶返回值、帶參和不帶參的函數委託萬能的函數包裝器執行

//注意:Args&&表示Universal引用,因爲這裏的參數可能被左值或右值初始化
//      Funciont&&也爲Universal引用,如被lambda表達式初始化
template<typename Function, class...Args>
auto FuncWrapper(Function&& func, Args&& ...args)->decltype(func(std::forward<Args>(args)...))
{
    return func(std::forward<Args>(args)...);
}

void test0()
{
    cout << "void test0()" << endl;
}

int test1()
{
    return 1;
}

int test2(int x)
{
    return x;
}

string test3(string s1, string s2)
{
    return s1 + s2;
}

int main()
{
    
    FuncWrapper(test0);
    
    cout << "int test1(): "; 
    cout << FuncWrapper(test1) << endl;
    
    cout << "int test2(int x): " ;
    cout << FuncWrapper(test2, 1) << endl;
    
    cout << "string test3(string s1, string s2): ";
    cout << FuncWrapper(test3, "aa", "bb") << endl;
    
    cout << "[](int x, int y){return x + y;}: ";
    cout << FuncWrapper([](int x, int y){return x + y;}, 1,  2) << endl;
    
    return 0;
}

輸出結果:

e:\Study\C++11\16>g++ -std=c++11 test3.cpp
e:\Study\C++11\16>a.exe
void test0()
int test1(): 1
int test2(int x): 1
string test3(string s1, string s2): aabb
[](int x, int y){return x + y}: 3

(2)emplace_back減少內存拷貝和移動

①emplace_back的實現原理類似於“萬能函數包裝器”,將參數std::forward轉發給元素類的構造函數。實現上,首先爲該元素開闢內存空間,然後在這片空間中調用placement new進行初始化,這相當於“就地”(在元素所在內存空間)調用元素對象的構造函數。

②而push_back會先將參數轉爲相應的元素類型,這需要調用一次構造函數,再將這個臨時對象拷貝構造給容器內的元素對象,所以共需要一次構造和一次拷貝構造。從效率上看不如emplace_back,因爲後者只需要一次調用一次構造即可。

③一般傳入emplace_back的是構造函數所對應的參數(也只有這樣傳參才能節省一次拷貝構造),所以要求對象有相應的構造函數,如果沒有對應的構造函數,則只能用push_back,否則編譯會報錯。如emplace_back(int, int),則要求元素對象需要有帶兩個int型的構造函數。

【編程實驗】emplace_back減少內存拷貝和移動

#include <iostream>
#include <vector>

using namespace std;

class Test
{
    int m_a;
public:
    static int m_count;
    
    Test(int a) : m_a(a)
    {
        cout <<"Test(int a)" << endl;
    }
    
    Test(const Test& t) : m_a(t.m_a)
    {
        ++m_count;
        cout << "Test(const Test& t)" << endl;
    }
    
    Test& operator=(const Test& t)
    {
        this->m_a = t.m_a;
        return *this;
    }
};

int Test::m_count = 0;

int main()
{
    //創建10個值爲1的元素
    Test::m_count = 0;
    vector<Test> vec(10, 1); //首先將1轉爲Test(1),會調用1次Test(int a)。然後,利用Test(1)去拷貝構造10個元素,所以
                             //調用10次拷貝構造。
    cout << "vec.capacity():" << vec.capacity() << ", "; //10
    cout << "vec.size():" << vec.size() <<  endl;        //10,空間己滿
    
    Test::m_count = 0;
    vec.push_back(Test(1)); //由於capacity空間己滿。首先調用Test(1),然後再push_back中再拷貝
                            //構造10個元素(而不是1個,爲了效率),所以調用10次拷貝構造
    cout << "vec.capacity():" << vec.capacity() << ", ";  //20
    cout << "vec.size():" << vec.size() <<  endl;         //11,空間未滿
    
    Test::m_count = 0;
    vec.push_back(1);  //先調用Test(1),然後調用1次拷貝構造
    cout << "vec.capacity():" << vec.capacity() << ", "; //20
    cout << "vec.size():" << vec.size() <<  endl;         //12,空間未滿
    
    Test::m_count = 0;
    vec.emplace_back(1); //由於空間未滿,直接在第12個元素位置調用placement new初始化那段空間
                         //所以就會調用構造函數,節省了調用拷貝構造的開銷
    cout << "vec.capacity():" << vec.capacity() << ", "; //20
    cout << "vec.size():" << vec.size() <<  endl;        //13,空間未滿
    
    Test::m_count = 0;
    vec.emplace_back(Test(1)); //先調用Test(1),再調用拷貝構造(注意與vec.emplace_back(1)之間差異)
    cout << "vec.capacity():" << vec.capacity() << ", "; //20
    cout << "vec.size():" << vec.size() <<  endl;        //14,空間未滿
    
    return 0;
}

輸出結果

e:\Study\C++11\16>g++ -std=c++11 test4.cpp
e:\Study\C++11\16>a.exe
Test(int a)
...  //中間省略了調用10次Test(const Test& t)
vec.capacity():10, vec.size():10
Test(int a)
...  //中間省略了調用10次Test(const Test& t)
vec.capacity():20, vec.size():11
Test(int a)
Test(const Test& t)
vec.capacity():20, vec.size():12
Test(int a)
vec.capacity():20, vec.size():13
Test(int a)
Test(const Test& t)
vec.capacity():20, vec.size():14

在這裏插入圖片描述

發佈了95 篇原創文章 · 獲贊 48 · 訪問量 22萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章