2009年11月30日 星期一

雞婆的C++

AVI的應徵筆試裡有一題
What functions does c++ compiler write for your class silently?
答案是:
  1. Default Constructor 
  2. Destructor
  3. Copy Constructor
  4. Copy Assignment Operator
我相信有幾位同仁應該還記憶猶新。這個概念其實很簡單記也很重要,但很少人知道。

不過應該有不少人心裡嘀咕著"Compiler會產生什麼function關我什麼事?,我會寫class就好~"

其實是有差的。當你寫一個class的時候,如果不考慮這一點,很有可能就替你的client(意指使用你的class的人)設下陷阱。
什麼陷阱?小則leak,大則crash! (其實leak也不算小事啦)
假設你寫了一個很沒有意義的class如下
List 1 - Class TenBytes
class TenBytes
{
public:
 TenBytes() {mTenBytes = new char[10];}
 ~TenBytes() {if (mTenBytes) delete [] mTenBytes;}
private:
 char *mTenBytes;
};
一切看起來都很正常。在constructor配置了10 bytes,在destructor也有釋放掉,記憶體管理應該沒問題才對。乍看之下的確沒問題,但試想下列情況:
List 2 - Example
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的話,就很危險了。
List 2為例,在執行到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。
  2. 把copy constructor與copy assignment operator給隱藏起來(例:List 3)。
List 3 - Explicitly disallow copy constructor and copy assignment operator
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的個數通常都是固定的,沒必要有分身。

結語

我覺得成為一個成熟的programmer在開發程式的時候,不單要考慮到自己,更要考慮到別人,怎樣才能寫出一個用起來不容易出錯的class也是一個很重要的課題與目標。

4 則留言: