C++移動構造函數以及move語句簡單介紹

C++移動構造函數以及move語句簡單介紹

首先看一個小例子:

#include <iostream>
#include <cstring>
#include <cstdlib>
#include <vector>

using namespace std;

int main()
{
    string st = "I love xing";
    vector<string> vc ;
    vc.push_back(move(st));
    cout<<vc[0]<<endl;
    if(!st.empty())
        cout<<st<<endl;

    return 0;
}

 

結果爲:ios

 

#include <iostream>
#include <cstring>
#include <cstdlib>
#include <vector>

using namespace std;

int main()
{
    string st = "I love xing";
    vector<string> vc ;
    vc.push_back(st);
    cout<<vc[0]<<endl;
    if(!st.empty())
        cout<<st<<endl;

    return 0;
}

 

結果爲:小程序

 

這兩個小程序惟一的不一樣是調用vc.push_back()將字符串插入到容器中去時,第一段代碼使用了move語句,而第二段代碼沒有使用move語句。輸出的結果差別也很明顯,第一段代碼中,原來的字符串st已經爲空,而第二段代碼中,原來的字符串st的內容沒有變化函數

 

好,記住這兩端代碼的輸出結果之間的差別。下面咱們簡單介紹一下移動構造函數。

在介紹移動構造函數以前,咱們先要回顧一下拷貝構造函數。this

咱們都知道,C++在三種狀況下會調用拷貝構造函數(可能有紕漏),第一種狀況是函數形實結合時,第二種狀況是函數返回時,函數棧區的對象會複製一份到函數的返回去,第三種狀況是用一個對象初始化另外一個對象時也會調用拷貝構造函數。spa

除了這三種狀況下會調用拷貝構造函數,另外若是將一個對象賦值給另外一個對象,這個時候回調用重載的賦值運算符函數。設計

不管是拷貝構造函數,仍是重載的賦值運算符函數,我記得當時在上C++課的時候,老師再三強調,必定要注意指針的淺層複製問題。指針

這裏在簡單回憶一下拷貝構造函數中的淺層複製問題code

首先看一個淺層複製的代碼對象

#include <iostream>
#include <cstring>
#include <cstdlib>
#include <vector>

using namespace std;

class Str{
    public:
    char *value;
    Str(char s[])
    {
        cout<<"調用構造函數..."<<endl;
        int len = strlen(s);
        value = new char[len + 1];
        memset(value,0,len + 1);
        strcpy(value,s);
    }
    Str(Str &v)
    {
        cout<<"調用拷貝構造函數..."<<endl;
        this->value = v.value;
    }
    ~Str()
    {
        cout<<"調用析構函數..."<<endl;
        if(value != NULL)
            delete[] value;
    }
};

int main()
{

    char s[] = "I love BIT";
    Str *a = new Str(s);
    Str *b = new Str(*a);
    delete a;
    cout<<"b對象中的字符串爲:"<<b->value<<endl;
    delete b;
    return 0;
}

 

輸出結果爲:blog

 

 

首先結果並不符合預期,咱們但願b對象中的字符串也是I love BIT可是輸出爲空,這是由於b->value和a->value指向了同一片內存區域,當delete a的時候,該內存區域已經被收回,因此再用b->value訪問那塊內存其實是不合適的,並且,雖然我運行時程序沒有崩潰,可是程序存在崩潰的風險呀,由於當delete b的時候,那塊內存區域又被釋放了一次,兩次釋放同一塊內存,至關危險呀。

咱們用valgrind檢查一下,發現,至關多的內存錯誤呀!

 

 

其中就有一個Invalid free 也就是刪除b的時候調用析構函數,對已經釋放掉對空間又釋放了一次。

 

那麼深層複製應該怎樣寫呢?

代碼以下:

#include <iostream>
#include <cstring>
#include <cstdlib>
#include <vector>

using namespace std;

class Str{
    public:
    char *value;
    Str(char s[])
    {
        cout<<"調用構造函數..."<<endl;
        int len = strlen(s);
        value = new char[len + 1];
        memset(value,0,len + 1);
        strcpy(value,s);
    }
    Str(Str &v)
    {
        cout<<"調用拷貝構造函數..."<<endl;
        int len = strlen(v.value);
        value = new char[len + 1];
        memset(value,0,len + 1);
        strcpy(value,v.value);
    }
    ~Str()
    {
        cout<<"調用析構函數..."<<endl;
        if(value != NULL)
        {
            delete[] value;
            value = NULL;
        }
    }
};

int main()
{

    char s[] = "I love BIT";
    Str *a = new Str(s);
    Str *b = new Str(*a);
    delete a;
    cout<<"b對象中的字符串爲:"<<b->value<<endl;
    delete b;
    return 0;
}

 

結果爲:

 

 

此次達到了咱們預想的效果,並且,用valgrind檢測一下,發現,沒有內存錯誤!

 

 

 

因此,寫拷貝構造函數的時候,切記要注意指針的淺層複製問題呀

 

好的,回顧了一下拷貝構造函數,下面回到移動構造函數上來。

有時候咱們會遇到這樣一種狀況,咱們用對象a初始化對象b,後對象a咱們就不在使用了,可是對象a的空間還在呀(在析構以前),既然拷貝構造函數,實際上就是把a對象的內容複製一份到b中,那麼爲何咱們不能直接使用a的空間呢?這樣就避免了新的空間的分配,大大下降了構造的成本。這就是移動構造函數設計的初衷。

下面這個圖,很好地說明了拷貝構造函數和移動構造函數的區別。

 

 

看明白了嗎?

通俗一點的解釋就是,拷貝構造函數中,對於指針,咱們必定要採用深層複製,而移動構造函數中,對於指針,咱們採用淺層複製

可是上面提到,指針的淺層複製是很是危險的呀。沒錯,確實很危險,並且經過上面的例子,咱們也能夠看出,淺層複製之因此危險,是由於兩個指針共同指向一片內存空間,若第一個指針將其釋放,另外一個指針的指向就不合法了。因此咱們只要避免第一個指針釋放空間就能夠了。避免的方法就是將第一個指針(好比a->value)置爲NULL,這樣在調用析構函數的時候,因爲有判斷是否爲NULL的語句,因此析構a的時候並不會回收a->value指向的空間(同時也是b->value指向的空間)

因此咱們能夠把上面的拷貝構造函數的代碼修改一下:

#include <iostream>
#include <cstring>
#include <cstdlib>
#include <vector>

using namespace std;

class Str{
    public:
    char *value;
    Str(char s[])
    {
        cout<<"調用構造函數..."<<endl;
        int len = strlen(s);
        value = new char[len + 1];
        memset(value,0,len + 1);
        strcpy(value,s);
    }
    Str(Str &v)
    {
        cout<<"調用拷貝構造函數..."<<endl;
        this->value = v.value;
        v.value = NULL;
    }
    ~Str()
    {
        cout<<"調用析構函數..."<<endl;
        if(value != NULL)
            delete[] value;
    }
};

int main()
{

    char s[] = "I love BIT";
    Str *a = new Str(s);
    Str *b = new Str(*a);
    delete a;
    cout<<"b對象中的字符串爲:"<<b->value<<endl;
    delete b;
    return 0;
}

 

結果爲:

 

 

修改後的拷貝構造函數,採用了淺層複製,可是結果仍可以達到咱們想要的效果,關鍵在於在拷貝構造函數中,最後咱們將v.value置爲了NULL,這樣在析構a的時候,就不會回收a->value指向的內存空間。

 

這樣用a初始化b的過程當中,實際上咱們就減小了開闢內存,構形成本就下降了。

 

但要注意,咱們這樣使用有一個前提是:用a初始化b後,a咱們就不須要了,最好是初始化完成後就將a析構。若是說,咱們用a初始化了b後,仍要對a進行操做,用這種淺層複製的方法就不合適了。

因此C++引入了移動構造函數,專門處理這種,用a初始化b後,就將a析構的狀況。

 

*************************************************************

**移動構造函數的參數和拷貝構造函數不一樣,拷貝構造函數的參數是一個左值引用,可是移動構造函數的初值是一個右值引用。(關於右值引用你們能夠看我以前的文章,或者查找其餘資料)。這意味着,移動構造函數的參數是一個右值或者將亡值的引用。也就是說,只用用一個右值,或者將亡值初始化另外一個對象的時候,纔會調用移動構造函數。而那個move語句,就是將一個左值變成一個將亡值。

 

移動構造函數應用最多的地方就是STL中

給出一個代碼,你們自行驗證使用move和不適用move的區別吧

#include <iostream>
#include <cstring>
#include <cstdlib>
#include <vector>
using namespace std;

class Str{
    public:
        char *str;
        Str(char value[])
        {
            cout<<"普通構造函數..."<<endl;
            str = NULL;
            int len = strlen(value);
            str = (char *)malloc(len + 1);
            memset(str,0,len + 1);
            strcpy(str,value);
        }
        Str(const Str &s)
        {
            cout<<"拷貝構造函數..."<<endl;
            str = NULL;
            int len = strlen(s.str);
            str = (char *)malloc(len + 1);
            memset(str,0,len + 1);
            strcpy(str,s.str);
        }
        Str(Str &&s)
        {
            cout<<"移動構造函數..."<<endl;
            str = NULL;
            str = s.str;
            s.str = NULL;
        }
        ~Str()
        {
            cout<<"析構函數"<<endl;
            if(str != NULL)
            {
                free(str);
                str = NULL;
            }
        }
};
int main()
{
    char value[] = "I love zx";
    Str s(value);
    vector<Str> vs;
    //vs.push_back(move(s));
    vs.push_back(s);
    cout<<vs[0].str<<endl;
    if(s.str != NULL)
        cout<<s.str<<endl;
    return 0;
} 

 

若是你以爲對你有用,請咱一個吧~~~

相關文章
相關標籤/搜索