1. 程式人生 > 其它 >C++11——轉移和完美轉發

C++11——轉移和完美轉發

1. move
在 C++11 添加了右值引用,並且不能使用左值初始化右值引用如果想要使用左值初始化一個右值引用需要藉助 std::move () 函式

使用std::move方法可以將左值轉換為右值。使用這個函式並不能移動任何東西,而是和移動建構函式一樣都具有移動語義將物件的狀態或者所有權從一個物件轉移到另一個物件,只是轉移,沒有記憶體拷貝

從實現上講,std::move 基本等同於一個型別轉換:static_cast<T&&>(lvalue);,函式原型如下:

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)); }

使用方法如下:

class Test
{
    public:
        Test() {}
    ......
}
int main()
{
    Test t;
    Test&& v1 = t; // error
    Test&& v2 = move(t); //
ok return 0; }

在第 4 行中,使用左值初始化右值引用,因此語法是錯誤的
在第 5 行中,使用 move() 函式將左值轉換為了右值,這樣就可以初始化右值引用了。
假設一個臨時容器很大,並且需要將這個容器賦值給另一個容器,就可以執行如下操作:

list<string> ls;
ls.push_back("hello");
ls.push_back("world");
......
list<string> ls1 = ls; // 需要拷貝, 效率低
list<string> ls2 = move(ls);


如果不使用 std::move,拷貝的代價很大,效能較低。使用 move 幾乎沒有任何代價,只是轉換了資源的所有權

。如果一個物件內部有較大的堆記憶體或者動態陣列時,使用 move () 就可以非常方便的進行資料所有權的轉移。另外,我們也可以給類編寫相應的移動建構函式(T::T(T&& another))和和具有移動語義的賦值函式(T&& T::operator=(T&& rhs)),在構造物件和賦值的時候儘可能的進行資源的重複利用,因為它們都是接收一個右值引用引數。

2. forward
右值引用型別是獨立於值的,一個右值引用作為函式引數的形參時,在函式內部轉發該引數給內部其他函式時,它就變成一個左值,並不是原來的型別了。

如果需要按照參數原來的型別轉發到另一個函式,可以使用 C++11 提供的 std::forward () 函式,該函式實現的功能稱之為完美轉發

// 函式原型
template <class T> T&& forward (typename remove_reference<T>::type& t) noexcept;
template <class T> T&& forward (typename remove_reference<T>::type&& t) noexcept;

// 精簡之後的樣子
std::forward<T>(t);
當T為左值引用型別時t將被轉換為T型別的左值
當T不是左值引用型別時,t將被轉換為T型別的右值

下面通過一個例子演示一下關於 forward 的使用:

#include <iostream>
using namespace std;

template<typename T>
void printValue(T& t)
{
    cout << "l-value: " << t << endl;
}

template<typename T>
void printValue(T&& t)
{
    cout << "r-value: " << t << endl;
}

template<typename T>
void testForward(T&& v)
{
    printValue(v);
    printValue(move(v));
    printValue(forward<T>(v));
    cout << endl;
}

int main()
{
    testForward(520);
    int num = 1314;
    testForward(num);
    testForward(forward<int>(num));
    testForward(forward<int&>(num));
    testForward(forward<int&&>(num));

    return 0;
}

測試程式碼列印的結果如下:

l-value: 520
r-value: 520
r-value: 520

l-value: 1314
r-value: 1314
l-value: 1314

l-value: 1314
r-value: 1314
r-value: 1314

l-value: 1314
r-value: 1314
l-value: 1314

l-value: 1314
r-value: 1314
r-value: 1314

testForward(520); 函式的形參為未定引用型別 T&&,實參為右值,初始化後被推導為一個右值引用
printValue(v); 已命名的右值 v,編譯器會視為左值處理,實參為左值
printValue(move(v)); 已命名的右值編譯器會視為左值處理,通過 move 又將其轉換為右值,實參為右值
printValue(forward<T>(v));forward 的模板引數為右值引用,最終得到一個右值,實參為 ``右值`

testForward(num); 函數的形參為未定引用型別 T&&,實參為左值,初始化後被推導為一個左值引用
printValue(v); 實參為左值
printValue(move(v)); 通過 move 將左值轉換為右值,實參為右值
printValue(forward<T>(v));forward 的模板引數為左值引用,最終得到一個左值引用,實參為左值


testForward(forward<int>(num));forward 的模板型別為 int最終會得到一個右值,函式的形參為未定引用型別 T&& 被右值初始化後得到一個右值引用型別
printValue(v); 已命名的右值 v,編譯器會視為左值處理,實參為左值
printValue(move(v)); 已命名的右值編譯器會視為左值處理,通過 move 又將其轉換為右值,實參為右值
printValue(forward<T>(v));forward 的模板引數為右值引用,最終得到一個右值,實參為右值


testForward(forward<int&>(num));forward 的模板型別為 int&,最終會得到一個左值,函式的形參為未定引用型別 T&& 被左值初始化後得到一個左值引用型別
printValue(v); 實參為左值
printValue(move(v)); 通過 move 將左值轉換為右值,實參為右值
printValue(forward<T>(v));forward 的模板引數為左值引用,最終得到一個左值,實參為左值


testForward(forward<int&&>(num));forward 的模板型別為 int&&,最終會得到一個右值,函式的形參為未定引用型別 T&& 被右值初始化後得到一個右值引用型別
printValue(v); 已命名的右值 v,編譯器會視為左值處理,實參為左值
printValue(move(v)); 已命名的右值編譯器會視為左值處理,通過 move 又將其轉換為右值,實參為右值
printValue(forward<T>(v));forward 的模板引數為右值引用,最終得到一個右值,實參為右值