C++11 std::move

C++11 std::move

概述

本篇博客根據深入理解C++11新特性解析與應用一書中的內容以及自己在使用std::move過程中的經驗,總結形成該篇博客,將它整理形成知識。該書我已經高清書籤版上傳到CSDN,爲了防止不過,修改了文件名稱,下載地址奉上。
深入理解C++11新特性解析與應用

關鍵字std::move概述

在C++11中,標準庫在<utility>中提供了一個有用的函數std::move,這個函數的名字具有迷惑性,因爲實際上std::move並不能夠移動任何東西,它唯一的功能是將一個左值強制轉化爲右值引用,繼而我們可以通過右值引用使用該值,以用於移動語義。
下面是std::move可能的實現的一種方式。

template< class T >
typename std::remove_reference<T>::type&& move(T&& t) noexcept
{
	return static_cast<typename remove_reference<T>::type&&>(t);
}

從上訴代碼中,可以看出std::move基本等同於一個類型轉換:

statc_cast<T&&>(lvalue);

錯誤用例

需要注意的是被轉化的值,其生命期並沒有隨着左右值的轉換而改變,即被std::move轉化的左值變量lvalue並不會被立即析構。

#include <iostream>
using namespace std;

class Moveable{
public:
    Moveable():i(new int(3)) {}
    ~Moveable() { delete i; }
    Moveable(const Moveable & m): i(new int(*m.i)) { }
    Moveable(Moveable && m):i(m.i) {
        m.i = nullptr; 
    }
    int* i;
};

int main() {
    Moveable a;

    Moveable c(move(a));    // 會調用移動構造函數
    cout << *a.i << endl;   // 運行時錯誤
}

上述代碼中a本來是一個左值變量,通過std::move將其轉換爲右值。這樣一來,a.i就被c的移動構造函數設置爲指針空值。由於a的生命週期要到main函數結束才結束,因此對表達式*a.i進行計算的時候,就會發生嚴重的運行時錯誤。
上述代碼是典型誤用std::move的例子。

正確用例1

要正確使用該函數,必須是程序員清楚需要轉換的時候,比如上例中,程序員應該知道被轉換爲右值的a不可以再使用。不過更多地,我們需要轉換成爲右值引用的還是一個確實聲明期即將結束的對象。下面給出正確的用例。

#include <iostream>
using namespace std;

class HugeMem{
public:
    HugeMem(int size): sz(size > 0 ? size : 1) {
        c = new int[sz];
    }
    ~HugeMem() { delete [] c; }
    HugeMem(HugeMem && hm): sz(hm.sz), c(hm.c) {
        hm.c = nullptr;
    }
    int * c;
    int sz;
};
class Moveable{
public:
    Moveable():i(new int(3)), h(1024) {}
    ~Moveable() { delete i; }
    Moveable(Moveable && m):
        i(m.i), h(move(m.h)) {      // 強制轉爲右值,以調用移動構造函數
        m.i = nullptr; 
    }
    int* i;
    HugeMem h;
};

Moveable GetTemp() { 
    Moveable tmp = Moveable(); 
    cout << hex << "Huge Mem from " << __func__ 
        << " @" << tmp.h.c << endl; // Huge Mem from GetTemp @0x603030
    return tmp;
}

int main() {
    Moveable a(GetTemp()); 
    cout << hex << "Huge Mem from " << __func__ 
        << " @" << a.h.c << endl;   // Huge Mem from main @0x603030
}

上述例子我們定義了兩個類型:HugeMemMoveable,其中Moveable包含了一個HugeMem的對象。在Moveable的移動構造函數中,我們使用了std::move,它將m.h強制轉換爲右值,以使用Moveable中的h能夠實現移動構造,由於GetTemp()返回的是右值,因此m將在表達式結束後被析構,其成員自然也被析構。

正確用例2

#include <iostream>
#include <utility>
#include <vector>
#include <string>
 
int main()
{
    std::string str = "Hello";
    std::vector<std::string> v;
 
    // uses the push_back(const T&) overload, which means 
    // we'll incur the cost of copying str
    v.push_back(str);
    std::cout << "After copy, str is \"" << str << "\"\n";
 
    // uses the rvalue reference push_back(T&&) overload, 
    // which means no strings will be copied; instead, the contents
    // of str will be moved into the vector.  This is less
    // expensive, but also means str might now be empty.
    v.push_back(std::move(str));
    std::cout << "After move, str is \"" << str << "\"\n";
 
    std::cout << "The contents of the vector are \"" << v[0]
                                         << "\", \"" << v[1] << "\"\n";
}

總結

事實上,爲了保證移動語義的傳遞,程序員在編寫移動構造函數的時候,應該總是記得使用std::move轉換擁有的形如堆內存、文件句柄等資源的成員爲右值,這樣一來,如果成員支持移動構造的話,就可以實現其移動語義。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章