TODO
左右值引用,完美轉發,RVO,重新修改文章
前言
目前還是初稿,文章有待更改,參考資料都爲好文,學習自用,有錯麻煩提一下
左右值
左右值判斷
-
官方定義:cpprefernce中文和cpp英文
之所以要認識左右值是因爲後面要使用移動語義,而我們不需要那麼精確的左右值定義,只需要知道變量是左值,其他的:函數返回,字面值等很容易看出來是右值
一些觀念
-
等號左邊是左值,等號右邊是右值
這是最初的定義
-
看能不能對錶達式取地址,如果能,則爲左值,否則爲右值
移動語義
右值是可以很安全的被"移動"的,operator=賦值的時候調用移動構造函數,比左值賦值時候調用的拷貝構造函數更高效
移動語義就是
移動語義的原理
略,只是類似於簡單的交換指針一樣
std::move作用:
將左值轉換爲右值,從而方便應用移動語義
使用場景和方法:
-
成員函數中shared_ptr的傳參並複製給類成員變量
用shared_ptr要拷貝,一個shared_ptr對象,因爲shared_ptr對象是線程不安全的,在這裏用到shared_ptr會有問題(在構造函數時線程不安全嗎),用移動構造函數就不會了(爲什麼呢TODO)
-
unique_ptr,見下面[不可複製對象用移動]部分
-
void push_back( const T& value ); // (1) void push_back( T&& value ); // 定義賦值函數 vector<vector<string>> vv; vector<string> v = {"123", "456"}; v.push_back("789"); // 臨時構造的string類型右值被移動進容器v vv.push_back(move(v)); // std::move的用法,顯式將v移動進vv
-
同樣是使用vector的情況
class Mystring;//詳細定義略,假定他的佔用很大 MyString(MyString&& str) noexcept{};//移動構造函數,詳細內容略 vector<MyString> vecStr; vecStr.reserve(1000); //先分配好1000個空間,不這麼做,調用的次數可能遠大於1000 for(int i=0;i<1000;i++){ vecStr.push_back(MyString("hello")); } //這裏使用移動語義的應用就是定義一個移動構造函數, //這樣編譯器會自動優化,利用移動構造函數而不是拷貝構造來傳入vector中
移動語義的作用
使用移動來代替構造更高的效率
情況一:
vector<string> str_split(const string& s) {
vector<string> v;
// ...
return v; // v是左值,但優先移動,不支持移動時仍可複製。
}
情況二:
//同理上面,用移動構造函數來代替拷貝構造函數,用移動賦值代替複製賦值
vector<string> str_split(const string& s);
vector<string> v = str_split("1,2,3"); //1
vector<string> v2; //2
v2 = str_split("1,2,3");//3
對於上面這段代碼,沒有移動語義的時候應該是:
-
第一句,棧上構造個v(原答案說是堆上,爲什麼),然後用拷貝構造函數構造臨時值後,拷貝構造給v,再析構臨時對象
-
第三句,拷貝構造臨時對象後,先清理v2原本的數據,再拷貝賦值給v2,析構釋放臨時對象內存
如果使用移動語義後:
-
第一句,函數返回的vector用以移動構造對象v。而v不需要額外申請空間,直接取走臨時對象的堆上內存(臨時對象可以是堆上的嗎?),之後臨時對象成爲空殼,析構時也無需釋放堆內存。
-
第三句,返回值移動構造給v2,v2原本的數據先被清理掉,然後不用釋放內存
情況三:
std::vector的增長
當vector的存儲容量需要增長時,通常會重新申請一塊內存,並把原來的內容一個個複製過去並刪除。複製並刪除,這裏改用移動語義就夠了。對vector<string>這樣的容器帶來性能改變巨大
不可複製對象用移動
不可複製對象某些情況下可以使用移動語義,std::thread和unqiue_ptr
unique_ptr<SomeObj> create_obj(/*...*/) {return unique_ptr<SomeObj>(new SomeObj(/*...*/));
}//返回unqiue_ptr,因爲函數傳參返回要用到拷貝構造,只能移動語義
-
比如把unque_ptr放入vector中,因爲vector重新分配空間時會複製對象到另一塊內存,所以只能移動
額外知識
回值優化(RVO/NRVO, RVO, Return Value Optimization 返回值優化,或者NRVO, Named Return Value Optimization)
可見參考資料5,TODO找出inside cpp object model的例子
完美轉發std::forword
簡單的說,與std::move區別就是,move會把參數轉換成右值,而std::forward是把參數保留左右值
應用場景:
template <typename T>//(1)
void relay(T&& t) { func(t);}
////////
template <typename T>//(2)
void relay(T&& t) { func(std::forward<T>(t));}
以上這個realay函數作用是把參數傳進func並調用,在一版本中,因爲t是具名的,所以編譯器認爲他是一個左值,調用的時候會傳入左值
TODO:給個回調函數的例子