Pages

2011年10月9日 星期日

copy constructor copy assignment operator pointer


What functions does c++ compiler write for your class silently?
答案是:
1.  Default Constructor 
2.  Destructor
3.  Copy Constructor
4.  Copy Assignment Operator

其實是有差的。當你寫一個class的時候,如果不考慮這一點,很有可能就替你的client(意指使用你的class的人)設下陷阱。小則leak,大則crash!
假設你寫了一個很沒有意義的class如下

example 1
class TenBytes
{
public:
 TenBytes() {mTenBytes = new char[10];}
 ~TenBytes() {if (mTenBytes) delete [] mTenBytes;}
private:
 char *mTenBytes;
};


一切看起來都很正常。在constructor配置了10 bytes,在destructor也有釋放掉,記憶體管理應該沒問題才對。乍看之下的確沒問題,但試想下列情況:

example 2
TenBytes firstTenBytes;

TenBytes secondTenBytes = firstTenBytes;//Copy Constructor is invoked
TenBytes thirdTenBytes;
thirdTenBytes = firstTenBytes; //Copy Assignment is invoked


由於TenBytes這個class並未override copy constructor與copy assignment operator,
所以compiler會很雞婆地幫我們產生一個shallow(膚淺) copy constructor與shallow copy assignment operator。
所謂的shallow的意思就是只是把該class中member的值直接給了另外一個物件,
這在大部分的狀況(如果member都是scalar例如int, float的話)都很合理也沒問題。
但如果是pointer的話,就很危險了。

以第二個example,在執行到line 2時,程式把firstTenBytes的mTenBytes的值複製給secondTenBytes的mTenBytes了,意思就是firstTenBytes的mTenBytes與secondTenBytes的mTenBytes都指向同一個位址了。
類似的事情在line 4也發生了,因為程式把firstTenBytes的mTenBytes的值複製給thirdTenBytes的mTenBytes。所以這裡之後會發生兩件事,leak與crash。
Leak 是因為thirdTenBytes在constructor配置了10 bytes但mTenBytes所指向的位址因為line 4的assignment給覆蓋掉了。
Crash是因為firstTenBytes,secondTenBytes與thirdTenBytes的mTenBytes都指向同一個位址,所以第一個被destruct的物件會釋放掉那10 bytes,當第二個物件被destruct的時候會嘗試去釋放同一個記憶體,所以此時就會crash。

該如何預防呢?
這有兩個選擇:
1.  乖乖地實作copy constructor與copy assignment operator。(ex3)
2.  把copy constructor與copy assignment operator給隱藏起來。(ex4)

example3

class TenBytes
{
public:
 TenBytes() {mTenBytes = new char[10];}
 ~TenBytes() {if (mTenBytes) delete [] mTenBytes;}
TenBytes(const TenBytes &inTenBytes);
   TenBytes& operator = (const TenBytes &inTenBytes);  
private:
 char *mTenBytes;
};

example4

class TenBytes
{
public:
 TenBytes() {mTenBytes = new char[10];}
 ~TenBytes() {if (mTenBytes) delete [] mTenBytes;}
private:
 char *mTenBytes;
 TenBytes(const TenBytes &inTenBytes);
 TenBytes& operator = (const TenBytes &inTenBytes);  
};

我的習慣是在寫一個class的一開始就把所有method的殼都寫好,
大部分在做model class的時候應該都是選1,
而在做controller class的時候都選2。
因為data model理應可以copy才自然,才合理,
而controller的個數通常都是固定的,沒必要有分身。

沒有留言:

張貼留言