預設建構式與解構式有許多的地雷,首先,如先前所說,C++在你沒有撰寫此兩個函式的時候,會自動生成,那麼因為建構式支援多載,預設解構式還會生成嗎? 答案是不會的,以下以範例說明:
class ClassHasDefaultConstruct
{
};
class ClassHasNoDefaultConstruct
{
public:
ClassHasNoDefaultConstruct(int i){}
};
我們可以注意到第二個範例,有寫建構式但不是預設建構式(沒有任何參數的),此時C++就放棄幫你建立預設建構式了,可是有時候,我們在使用STL函式庫的時候,強迫要求你設計預設建構式,所以ClassHasNoDefaultConstruct這個類別是不能為STL容器所用。
這裡有個小伎倆,把有參數的建構式當成預設建構式用,利用預設參數的方式:
class ClassHasNoDefaultConstruct2
{
public:
ClassHasNoDefaultConstruct2(int i=0){}
};
使用建構式有一些注意事項,如果你的類別支援繼承,在建構式中,不可呼叫虛擬函式,因為,物件建構的順序是:基底類別建構式完成後,然後才是衍生類別建構,所以在基底類別建構式中呼叫虛擬函式,永遠是基底類別的函式
其次養成習慣,最好在建構式中,給予所有變數初值,避免執行期間產生錯誤,因為一般而言,C++在除錯版的時候,會自動給定變數初值(0, NULL),而Release版不會,變數值是看當時配置的記憶體資料內容而定,所以典型的程式描述是"我Debug版跑起來都沒錯,但是Release版就是會當機..."。
class BadSample
{
private:
char *ptr;
public:
BadSample(){};
~BadSample()
{
if( ptr ) delete ptr; // 當機
};
};
第三是當類別資料copy涉及記憶體時,永遠要寫複製建構式與operator =(); 或者,類別不允許這兩個函式的執行,原因很簡單,以下以程式說明之:
class CopyConstructor
{
private:
char *ptr;
public:
void ANewFunction()
{
ptr=new char[1024];
}
~CopyConstructor()
{
if( ptr ) delete[] ptr; // 假設建構式有把ptr=NULL;
}
};
以下例子解構時都會當機,因為ptr會被釋放多次:
CopyConstructor a;
a.ANewFunction();
CopyConstructor b(a); // b物件刪除的時候,ptr釋放一次,等到a物件刪除時,又一次
CopyConstructor c;
c=a; // 同上
所以要嗎你把類別這麼寫:
class CopyConstructor
{
private:
char *ptr;
public:
void ANewFunction()
{
ptr=new char[1024];
}
CopyConstructor(const CopyConstructor& c )
{
ptr=new char[1024]; // 各自配置各自的記憶體
memcpy( ptr, c.ptr, 1024 );
}
void operator =(const CopyConstructor& c ) // 簡化起見,不討論回傳值
{
ptr=new char[1024]; // 各自配置各自的記憶體
memcpy( ptr, c.ptr, 1024 );
}
~CopyConstructor()
{
if( ptr ) delete[] ptr; // 假設建構式有把ptr=NULL;
}
};
要嗎你把類別這麼寫:
class CopyConstructor
{
private:
char *ptr;
public:
void ANewFunction()
{
ptr=new char[1024];
}
CopyConstructor(const CopyConstructor& c )
{
ASSERT(0); //給他當機
}
void operator =(const CopyConstructor& c ) // 簡化起見,不討論回傳值
{
ASSERT(0); //給他當機
}
~CopyConstructor()
{
if( ptr ) delete[] ptr; // 假設建構式有把ptr=NULL;
}
};
解構式的注意事項是,如果想讓後續類別繼承,一定要用virtual,讓繼承解構的順序正確,還有,同建構式般,不可以在解構式中,呼叫虛擬函式,因為解構是繼承物件先解構,然後才基底物件,衍生物件已經不見了,基底物件去哪呼叫正確的虛擬函式呢?