最近在寫C++時,有這樣一個代碼需求:在lambda中,將一個捕獲參數move給另一個變量。 看似一個很簡單常規的操做,然而這個move動做卻沒有生效。c++
具體代碼以下:bash
std::vector<int> vec = {1,2,3};
auto func = [=](){
auto vec2 = std::move(vec);
std::cout << vec.size() << std::endl; // 輸出:3
std::cout << vec2.size() << std::endl; // 輸出:3
};
複製代碼
代碼可在wandbox運行。閉包
咱們指望的是,將對變量vec
調用std::move後,數據將會移動至變量vec2
, 此時vec
裏面應該沒有數據了。可是經過打印vec.size()
發現vec中的數據並無按預期移走。函數
這也就意味着,構造vec2時並無按預期調用移動構造函數,而是調用了拷貝構造函數。ui
爲何會形成這個問題呢, 咱們須要結合std::move
和lambda
的原理看下。(最終的解決方案能夠直接看這裏)spa
對於std::move,有兩點須要注意:code
對於第二點來講,答案顯然是不能。這也是本文的問題所在。那麼std::move其實是作了什麼事情呢?對象
對於std::move,其實現大體以下:rem
template<typename T>
decltype(auto) move(T&& param)
{
using ReturnType = remove_reference_t<T>&&;
return static_cast<ReturnType>(param);
}
複製代碼
從代碼能夠看出,std::move本質上是調用了static_cast作了一層強制轉換,強制轉換的目標類型是remove_reference_t<T>&&
,remove_reference_t是爲了去除類型自己的引用,例如左值引用。總結來講,std::move本質上是將對象強制轉換爲了右值引用。get
那麼,爲何咱們一般使用std::move實現移動語義,能夠將一個對象的數據移給另一個對象?
這是由於std::move配合了移動構造函數使用,本質上是移動構造函數起了做用。移動構造函數的通常定義以下:
class A{
public:
A(A &&);
};
複製代碼
能夠看到移動構造函數的參數就是個右值引用A&&
,所以 A a = std::move(b);
, 本質上是先將b強制轉化了右值引用A&&
, 而後觸發了移動構造函數,在移動構造函數中,完成了對象b的數據到對象a的移動。
那麼,在哪些狀況下,A a = std::move(b);
會失效呢?
顯然是,當std::move強轉後的類型不是A&&
,這樣就不會命中移動構造函數。
例如:
const std::string str = "123"
std::string str2(std::move(str));
複製代碼
這個時候,對str對象調用std::move
,強轉出來的類型將會是const string&&
, 這樣移動構造函數就不會起做用了,可是這個類型卻能夠令複製構造函數生效。
結合本文最初的問題,在lambda中move沒有生效,顯然也是std::move強轉的類型不是std::vector<int>&&
, 才致使了沒有move成功。
那麼,爲何會出現這個問題呢,咱們須要理解下lambda的工做原理。
對於c++的lambda,編譯器會將lambda轉化爲一個獨一無二的閉包類。而lambda對象最終會轉化成這個閉包類的對象。 對於本文最初的這個lambda來講,最終實際上轉化成了這麼一個類型
// 轉換前
auto func = [=](){
auto vec2 = std::move(vec);
};
// 轉換後
class ClosureFunc{
public:
void operator() const{
auto vec2 = std::move(vec);
};
private:
std::vector<int> vec;
};
ClosureFunc func;
複製代碼
這裏須要注意, lambda的默認行爲是,生成的閉包類的operator()
默認被const修飾。
那麼這裏問題就來了,當調用operator()
時, 該閉包類全部的成員變量也是被const修飾的,此時對成員變量調用std::move
將會引起上文中提到的,強轉出來的類型將會是const string&&
問題。所以,移動構造函數將不會被匹配到。
咱們最初的問題lambda中std::move失效的問題,也是由於這個緣由。這也很符合const函數的語義: const函數是不能修改爲員變量的值。
那麼,這個應該怎麼解決呢?答案是mutable
。即在lambda尾部聲明一個mutable,以下:
auto func = [=]() mutable{
auto vec2 = std::move(vec);
};
複製代碼
這樣編譯器生成的閉包類的operator()
將會不帶const了。咱們的std::move也能夠正常轉換,實現移動語義了。
std::vector<int> vec = {1,2,3};
auto func = [=](){
auto vec2 = std::move(vec);
std::cout << vec.size() << std::endl; // 輸出:0
std::cout << vec2.size() << std::endl; // 輸出:3
};
複製代碼
代碼能夠在wandbox運行。