C++11中std::move、std::forward、左右值引用、移動建構函式的測試

huik發表於2020-09-07

關於C++11新特性之std::move、std::forward、左右值引用網上資料已經很多了,我主要針對測試效能做一個測試,梳理一下這些邏輯,首先,左值比較熟悉,右值就是臨時變數,意味著使用一次就不會再被使用了。針對這兩種值引入了左值引用和右值引用,以及引用摺疊的概念。

1.右值引用的舉例測試

#include <iostream>
using namespace std;
​
//建立一個測試類
class A
{
public:
    A() : m_a(55)
    {
    }
​
    int m_a;
};
​
void funcA(A&& param) // 右值引用引數,只接受右值
{
    cout << param.m_a << endl; // param與a的地址一致,僅僅只是取了一個新名字
}
​
int main()
{
    A a;
    funcA(move(a)); //必須將其轉換為右值
    cout << a.m_a << endl; //正常列印,所以std::move並沒有移動的能力
    return 0;
}

2.左值和右值引用的舉例測試,以及引出萬能引用

構造一組過載函式,分別接受右值,和左值的引數,還有const A&的引數過載函式。

void funcA(const A& param)//既可以接受右值引用,也可以接受左值引用,但是有一個隱式轉換const A&
void funcA(A& param)// 接受左值引用 
void funcA(A&& param) // 接受右值引用

const A& param既可以接受右值引用,也可以接受左值引用,但是存在一個隱式轉換,const使用受限制。

#include <iostream>
using namespace std;
​
//建立一個測試類
class A
{
public:
    A() : m_a(55) // 建構函式
    {
        cout << "Constructor" << endl;
    }
    A(const A & other) : m_a(55) // copy建構函式
    {
        cout << "Copy Constructor" << endl;
        if (this == &other)
        {
            return;
        }
        this->m_a = other.m_a;
    }
    A& operator=(const A& other) // 賦值建構函式
    {
        cout << "= Constructor" << endl;
        if (this == &other)
        {
            return *this;
        }
        this->m_a = other.m_a;
        return *this;
    }
    int m_a;
};
void test(A&& pa) //測試是否為右值
{
    cout << "只接受右值" << endl;
}
void funcA(const A& param) // 既可以接受右值引用,也可以接受左值引用,但是有一個隱式轉換const A&
{
    //test(param);  //編譯不過,param可以接受右值,但是param被轉換為const左值
    //test(std::forward<A>(param));  //編譯不過,param可以接受右值,但是param被轉換為const左值
    cout << param.m_a << endl; 
}
void funcA(A& param) // 接受左值引用 
{
    //test(param);  //編譯不過,param可以接受右值,但是param被轉換為左值
    test(std::forward<A>(param));  //編譯通過,通過forward轉發
    cout << param.m_a << endl;
}
void funcA(A&& param) // 接受右值引用
{
    //test(param);  //編譯不過,param被轉換為左值
    test(std::forward<A>(param));  //編譯通過,通過forward轉發
    cout << param.m_a << endl;
}
​
int main()
{
    A a;
    const A& b = a;
    funcA(a);
    funcA(move(a));
    funcA(b);
    cout << a.m_a << endl; //正常列印,所以std::move並沒有移動的能力
    return 0;
}

對此C++11引入了萬能引用的概念,使得不需要那麼多的過載函式,既可以接受右值引用,也可以接受左值引用。但是函式內部,再需要呼叫一個左值或者右值的函式時,我們就得需要forward模版類。

#include <iostream>
using namespace std;
​
//建立一個測試類
class A
{
public:
    A() : m_a(new int(55)) // 建構函式
    {
        cout << "Constructor" << endl;
    }
    A(const A & other) : m_a(new int(55)) // copy建構函式
    {
        cout << "Copy Constructor" << endl;
        if (this == &other)
            return;
        this->m_a = other.m_a;
    }
    A& operator=(const A& other) // 賦值建構函式
    {
        cout << "= Constructor" << endl;
        if (this == &other)
            return *this;
​
        this->m_a = other.m_a;
        return *this;
    }
    int* m_a;
};
void test(A&& pa) //測試是否為右值
{
    cout << "只接受右值" << endl;
}
void test(A& pa) //測試是否為左值
{
    cout << "只接受左值" << endl;
}
​
template<class T>
void funcA(T&& param)
{
    test(std::forward<T>(param));  //編譯通過,通過forward完美轉發
    cout << *param.m_a << endl;
}
​
int main()
{
    A a;
    funcA(a);
    funcA(move(a));
    cout << *a.m_a << endl; //正常列印,所以std::move並沒有移動的能力
    return 0;
}

3.移動建構函式的引出

以上的所有特性,所能體現出來的是我們對於臨時變數的使用,儘可能的使用中間生成的臨時變數,提高效能,所謂的榨取最後的效能。移動建構函式注意的兩點

1.呼叫移動建構函式時引數(被移動者)必須是右值。

2.呼叫移動建構函式後被移動者就不能再被使用。

#include <iostream>
using namespace std;
​
//建立一個測試類
class A
{
public:
    A() : m_a(new int(55)) // 建構函式
    {
        cout << "Constructor" << endl;
    }
    A(const A & other) : m_a(new int(55)) // copy建構函式
    {
        cout << "Copy Constructor" << endl;
        if (this == &other)
        {
            return;
        }
        this->m_a = other.m_a;
    }
    A& operator=(const A& other) // 賦值建構函式
    {
        cout << "= Constructor" << endl;
        if (this == &other)
        {
            return *this;
        }
        this->m_a = other.m_a;
        return *this;
    }
​
    A(A&& other) : m_a(other.m_a) // 移動建構函式,引數是一個右值,
    {
        cout << "Move Constructor" << endl;
        if (this == &other)
        {
            return;
        }
        other.m_a = nullptr; //移動後將被移動的物件資料清空
    }
​
    int* m_a;
};
void test(A&& pa) //測試是否為右值
{
    cout << "只接受右值" << endl;
}
void test(A& pa) //測試是否為左值
{
    cout << "只接受左值" << endl;
}
​
template<class T>
void funcA(T&& param)
{
    test(std::forward<T>(param));  //編譯通過,通過forward完美轉發
    cout << *param.m_a << endl;
}
​
int main()
{
    A a;
    funcA(a);
    funcA(move(a));
    A b(move(a)); //呼叫移動建構函式,新的物件是b物件
    cout << *a.m_a << endl; //資料已被移動,程式崩潰
    return 0;
}

移動建構函式一定程度上較少了臨時記憶體的申請,減少不必要的拷貝,節省了空間和時間。以上特性在使用中還有很多需要注意的地方,如果我遇到了會及時的新增到這裡,分享給大家,一起加油。

相關文章