深入理解Move Constructor和Move Assignment

    之前的一篇博客講了左值引用和右值引用,並且細緻地說到了右值實際上就是“一閃而過”的結果,它是程序在計算時候的一箇中間量,這個中間量如果我們能夠有效的利用上,那麼我們再創建對象(Constructor)的時候或是給已有對象(Assignment)賦值的時候很多資源就不用再重新申請了,直接把右值的資源“steal”過來。不要小看這一步的改變,如果你的程序頻繁地創建對象,並且一個對象創建需要進行申請很多內存資源和計算的話,那麼這一小步就能夠在效率上提高很多。基於以上的思想,C++11中引入Move Constructor和Move Assignment。下面我們寫一段程序,理解一下這兩個新夥伴。

class Object{
public:
    Object (string* resource) : _resource(resource){
        
    }
    Object (Object& object) : _resource(new string(*object._resource)){
        
    }
    Object (Object&& object) : _resource(object._resource){
        object._resource = nullptr;
    }
    Object& operator=(Object& object){
        if (&object != this){
            _resource = new string(*object._resource);
        }
        return *this;
    }
    Object& operator=(Object&& object){
        if (&object != this){
            if (_resource != nullptr){
                delete _resource;
            }
            _resource = object._resource;
            object._resource = nullptr;
        }
        return *this;
    }
    ~Object(){
        if (_resource != nullptr){
            delete _resource;
        }
    }
private:
    string* _resource = nullptr;
};

    可以看到在Object類中與以往相比有兩個新成員函數:Object (Object&& object)和Object& opreator=(Object&& object),他們就是Move Constructor和Move Assignment。可以看到他們的特點是輸入參數是右值引用,他們“steal”輸入參數中的資源,節省資源提高效率,充分利用右值引用的特點,其實很好理解。

      但是如果想要深入理解Move Constructor和Move Assignment,需要注意的還有很多。

      1.衆所周知,對於一個類,如果我們不定義它的Constructor、Copy Constructor、Copy Assignment和Destructor,編譯器能夠幫我們合成這些必備的函數。但是對於Move Constructor和Move Assignment,如果我們不定義他們,編譯器也不會定義他們。但是有一個特例,就是如果一個類中如果他的成員變量都是可以“move”的(成員變量類自身定義了Move Constructor和Move Assignment),那麼編譯器會自動爲這個類生成Move Constructor和Move Assignment。

struct X{
    int i;            //build-in類型是可以“move”的
    string s;         //string是可以“move”的
};                   //所以編譯器能夠爲X合成Move Constructor和Move Assignment

struct Y{
    X x;            //X是可以“move”的
};                  //所以編譯器能夠爲Y合成ove Constructor和Move Assignment        

       我們對於想要編譯器合成的部分,可以使用using = default,但是對於Move Constructor和Move Assignment,仍然需要滿足一定條件,否則即使使用using = default,最後的結果也不能夠合成(using = delete)。a.在類中有成員變量不能夠“move”的類;b.對於沒有析構函數(desrucor = delete)的類(這種類能夠被new出來,但是不能夠被delete);c.有const或是引用的成員變量的類。

    2.對於一個沒有Move Constructor和Move Assignment的類,當傳入的參數是右值引用,那麼右值引用會轉換成const左值引用,然後調用該類的Copy Constructor和Copy Assignment。

     3.Copy-and-Swap Assignment Operator。這是一個有些trick的方式去定義Assignment Operator。還是上面的例子,我們可以這樣定義Assignment Operator:

Object& operator=(Object object){
    swap(*this, object);
    return *this;
}

和以往不同的是這裏的輸入參數不是引用,而是一個實例,這也正是這麼定義Assignment trick的地方。接着往下看:

object2 = object1                 //object1是左值,所以Copy Constructor將會被調用
object2 = std::move(object1)      //Move Constructor將會被調用

這下子一目瞭然了吧,一個Copy-and-Swap Assignment Operator可以替代Copy Assignment和Move Assignment。

    4.因爲對於Copy Constructor和Copy Assignment涉及到內存的重新分配,所以在在Copy Constructor和Copy Assignment中不要拋出異常,因爲一旦拋出異常,內存的分配狀況不確定,從而出現不確定行爲。

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