天天看點

C++11 std::move

C++11 std::move

文章目錄

    • 概述
    • 關鍵字```std::move```概述
    • 錯誤用例
    • 正确用例1
    • 正确用例2
    • 總結

概述

本篇部落格根據

深入了解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
}
           

上述例子我們定義了兩個類型:

HugeMem

Moveable

,其中

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

轉換擁有的形如堆記憶體、檔案句柄等資源的成員為右值,這樣一來,如果成員支援移動構造的話,就可以實作其移動語義。

繼續閱讀