拷貝構造函數與移動構造函數


一、拷貝構造函數

當類沒有定義拷貝構造函數的時候,編譯器會默認提供一個,這個拷貝函數是淺拷貝

如果該類中含有指針,可能會發生內存泄漏,見下面的例子:

class Test
{
 public:
 int *p;
  Test(){ p=new int; };
  ~Test(){ delete p; };
};
void main()
{
    Test t1;
    Test t2(t1);
    Test t3 = t1;
}

t1、t2、t3的成員變量p指向的是同一塊內存,程序結束后會出現重復釋放的問題。

為了解決這個問題,可以自定義拷貝構造函數:

class Test
{
 public:
 int *p;
 Test(const Test &t) { p = new int (*(t.p)); }
  Test(){ p=new int; };
  ~Test(){ delete p; };
};

二、右值引用

除了上述的解決方法,還可以使用C++11的【右值引用】新特性來解決,而且可以提高程序的性能,減少內存開銷。

為了引出左值引用的概念,先來復習左值和右值

1.左值和右值

int a = 3 + 4 ;

上面的式子中,變量 a 就是左值,右邊的表達式會生成一個臨時變量存放 (3+4) 的值,這個變量稱之為右值。

有兩種方式可以判斷:

(1)只能放在等號(=)右側的即為右值,可以放在左側的為左值

int a = 10 ;
10 = a ; //錯誤

(2)左值可以取地址,而右值不允許:

int a = 3 + 4 ;
int * b = & a ;  //ok
b = & (3+4) ; //錯誤

2.右值引用

 使用方法如下,b就是對右值 (3+4) 的引用。

int && b = 3 + 4 ;

先看下下面的左值引用:

int a = 0 ;
int &b = 4 ; //錯誤!
int &b = a ; //左值引用

如上例所示,左值引用只能對左值進行別名引用,無法引用右值

於是C++11增加了右值引用,使用 && 表示(和邏輯運算中的”且“一致)。

int a = 0 ;
int b = 1 ;
int && c = a+c ; //右值引用
int && c = 3 ; //右值引用
int && c = 3 +4 ; //右值引用
int && c = a ; //錯誤!

注意不能直接右值引用左值,C++提供了一個函數std::move()函數,可以將左值變成右值:

string str1 = "aa" ;
string && str2 = std::move( str1 );  //ok

 

3.右值引用的應用場景

(1)案例:

還是回到之前的例子:

class Test
{
 public:
 int *p;
 Test(const Test &t)
 {
     p = new int (*(t.p)); cout<<"copy construct"<<endl;
 }
  Test(){ p=new int; cout<<"construct"<<endl; };
  ~Test(){ delete p; cout<<"destruct"<<endl; };
};

Test getTest()
{
    return Test();
}

void main()
{
    {
        Test t = getTest();
    }
}

使用vs2012運行,結果為:

construct                 //執行 Test()
destruct                  //銷毀 t

 但需要注意的是,這是vs編譯器對拷貝構造函數優化后的結果。禁止優化,結果為:

construct                 //執行 Test()
copy construct            //執行 return Test()
destruct                  //銷毀 Test() 產生的匿名對象
copy construct            //執行 t = getTest()
destruct                  //銷毀 getTest() 返回的臨時對象
destruct                  //銷毀 t

可以看到,進行了兩次的深拷貝,對於對內存要求不高、本例這種占內存比較小的類Test而言(申請的堆空間小),可以接受。

但如果臨時對象中的指針成員申請了大量的堆空間,那將嚴重影響程序的執行效率。

C++11為了解決這一問題(深拷貝占用大量空間),引入移動構造函數

 (2)移動構造函數

所謂的移動,就是將其他的內存資源,“移為己有”,這些資源通常是臨時對象,比如上文所敘的右值

修改如下(增加一個移動構造函數):

class Test
{
 public:
 int *p;
 Test(Test &&t) //移動構造函數
 {
     p = t.p;
     t.p = nullptr;//將臨時對象的指針賦值為空
     cout<<"copy construct"<<endl;
 }
 Test(const Test &t) //拷貝構造函數
 {
     p = new int (*(t.p));
     cout<<"move construct"<<endl;
 }
  Test(){ p=new int; cout<<"construct"<<endl; };
  ~Test(){ delete p; cout<<"disconstruct"<<endl; };
};
Test getTest()
{
    return Test();
}
void main()
{
    {
        Test t = getTest();
    }
}

禁止vs優化,結果為:

construct                 //執行 Test()
move construct            //執行 return Test()
destruct                  //銷毀 Test() 產生的匿名對象
move construct            //執行 t = getTest()
destruct                  //銷毀 getTest() 返回的臨時對象
destruct                  //銷毀 t

可以看到,定義了移動構造函數后,臨時對象的創建使用移動構造函數創建,如下,沒有在堆上創建對象,減少了開銷。

 Test(Test &&t) //移動構造函數
 {
     p = t.p;
     t.p = nullptr;//將臨時對象的指針賦值為空
     cout<<"copy construct"<<endl;
 }

 

那么問題來了,什么時候調用移動構造函數,什么時候調用拷貝構造函數呢?將在后面的文章中分析。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM