cpp11移動語義完美轉發

TODO

左右值引用,完美轉發,RVO,重新修改文章

前言

目前還是初稿,文章有待更改,參考資料都爲好文,學習自用,有錯麻煩提一下

左右值

左右值判斷

之所以要認識左右值是因爲後面要使用移動語義,而我們不需要那麼精確的左右值定義,只需要知道變量是左值,其他的:函數返回,字面值等很容易看出來是右值

一些觀念

  • 等號左邊是左值,等號右邊是右值

    這是最初的定義

  • 看能不能對錶達式取地址,如果能,則爲左值,否則爲右值

移動語義

右值是可以很安全的被"移動"的,operator=賦值的時候調用移動構造函數,比左值賦值時候調用的拷貝構造函數更高效

移動語義就是

移動語義的原理

略,只是類似於簡單的交換指針一樣

std::move作用:

將左值轉換爲右值,從而方便應用移動語義

使用場景和方法:

  1. 成員函數中shared_ptr的傳參並複製給類成員變量

    用shared_ptr要拷貝,一個shared_ptr對象,因爲shared_ptr對象是線程不安全的,在這裏用到shared_ptr會有問題(在構造函數時線程不安全嗎),用移動構造函數就不會了(爲什麼呢TODO)

  2. unique_ptr,見下面[不可複製對象用移動]部分

  3. 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
  4. 同樣是使用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:給個回調函數的例子

參考資料

  1. 簡書的80贊

  2. csdn的,參考資料看看

  3. 左右值區分

  4. 清華的高贊

  5. [RVO/NRVO優化](https://blog.csdn.net/rlyhaha/article/details/80381170)

  6. [完美轉發](https://blog.csdn.net/suchto/article/details/54947998)

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章