我們定義了一個可以傳入右值引用的構造函數的類B,在使用std::move的時候,我們非常容易犯一個錯誤。看下面的代碼:
class B { public: B() :s(10), ptr(new int[s]) { std::cout << "default constructor" << std::endl; for (int i = 0; i < s; i++) { ptr[i] = i; } } B(const B& b) :s(b.s) { std::cout << "B&b" << std::endl; for (int i = 0; i < s; i++) ptr[i] = b.ptr[i]; } B(B&& b) :s(b.s), ptr(b.ptr) { std::cout << "B&& b" << std::endl; b.ptr = 0; } ~B() { std::cout << "~B()" << std::endl; if (ptr) delete[] ptr; } friend std::ostream& operator <<(std::ostream& os, B& b); private: int s; int* ptr; }; std::ostream& operator <<(std::ostream& os,B& b) { os << "[ "; for (int i = 0; i < b.s; i++) os << b.ptr[i] << " "; os << "]" << std::endl; return os; } B&& f()//注意這里B&& { B tmp; return std::move(tmp); } B& f2()//注意這里的B& { B t; return t; } int main() { B b0(f2()); B b1(f()): return 0 ; }
函數f2返回B的引用,但是B是一個臨時對象,馬上就會被析構,b0的構造函數傳入的參數是一個已經被析構的對象!大家能夠非常容易就看出錯誤所在。
但是,函數f返回std::move(tmp),不容易發現問題。
展開函數f():
首先一個B的臨時對象tmp被創建,使用std::move(tmp),將tmp由左值變為右值的引用,它是右值。編譯創建一個臨時對象B&& _tmp = std::move(tmp)。_tmp被傳遞給對象b1的構造函數。
無論是右值引用還是左值引用,它們都是引用。所以,f()和f2()發生了同樣的錯誤!
我們可以這樣修改:
B f() { B tmp; return std::move(tmp); }
展開函數f():
首先一個B的臨時對象tmp被創建,使用std::move(tmp),將tmp由左值變為右值的引用,它是右值。編譯創建一個臨時對象B _tmp = std::move(tmp)。_tmp調用構造函數B(B&&),轉移tmp的空間和大小。_tmp被傳遞給對象b1的構造函數。
以上。
再附上幾個容易混淆的代碼:
int& f1(int& a) { return a; }//正確,返回a的引用 int main() { int&& i = 123; int&& a = i;//錯誤,不能將左值賦給右值引用。如果你犯了這個錯誤,請務必思考清楚:引用對象在完成初始化后,這個對象的類型是什么。提示,i是一個具名引用。 int&& b = std::move(i);//正確 return 0; }
重新看了一遍C++11的書,關於引用類型感覺收獲了很多。
1.因為我們使用引用類型的變量,就跟使用所引用類型的變量一樣,導致我忘了引用類型是一種類型。比如:
int a = 1; int& b = a; int& c = b; b = c;
自從b被初始化后,用起來它就是一個int的變量一樣。實際上,它確實一個int的引用類型,而不是int。
2.關於C++11中模板的參數類型推斷,有兩個特殊情況。
template<typename T> void function(T&& a);
如果傳給function的實參是一個左值,則T被推斷為T&,而不是T。這是第一個特殊情況。
如果T& && 被折疊為T&,這是第二個特殊情況。