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 constructorcopy assignment operator
所以compiler會很雞婆地幫我們產生一個shallow(膚淺) copy constructorshallow copy assignment operator
所謂的shallow的意思就是只是把該classmember的值直接給了另外一個物件,
這在大部分的狀況(如果member都是scalar例如int, float的話)都很合理也沒問題。
但如果是pointer的話,就很危險了。

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

該如何預防呢?
這有兩個選擇:
1.  乖乖地實作copy constructorcopy assignment operator(ex3)
2.  copy constructorcopy 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的個數通常都是固定的,沒必要有分身。