概述
本篇博客根據深入理解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
轉換擁有的形如堆內存、文件句柄等資源的成員爲右值,這樣一來,如果成員支持移動構造的話,就可以實現其移動語義。