析構(gòu)函數(shù)
如果類對象成員指向一塊new分配的內(nèi)存,則需要編寫一個析構(gòu)函數(shù)來釋放內(nèi)存。當刪除類對象時,C++會釋放對象本身占用的內(nèi)存,但并不能自動釋放對象成員所指向的內(nèi)存,所以需要編寫相應(yīng)的析構(gòu)函數(shù)來釋放對象成員所指的內(nèi)存。
例如以下類聲明:
class Cowboy
{
private:
char* name;
public:
Cow();
Cow(const char* nm, double wt);
~Cow();
};
若構(gòu)造函數(shù)中為name分配了內(nèi)存,析構(gòu)函數(shù)需要釋放name所指的內(nèi)存。
復制構(gòu)造函數(shù)
下面用一個存在問題的類來說明復制構(gòu)造函數(shù)的作用。
假設(shè)存在如下類:
class Cowboy
{
private:
static int num_cows;
char* name;
double weight;
public:
Cow();
Cow(const char* nm, double wt);
~Cow();
void showCow();
};
其中num_cows為靜態(tài)類成員,它的特點是無論創(chuàng)建了多少對象,程序都只創(chuàng)建一個靜態(tài)類變量副本,所以可以用它來記錄創(chuàng)建的類對象數(shù)量。
類方法實現(xiàn)如下:
#include <cstring>
#include <iostream>
#include "Cow.h"
using namespace std;
int Cow::num_cows = 0;
Cow::Cow() {
name = nullptr;
weight = 0;
num_cows++;
cout << num_cows << ": \"" << name << "\" created" << endl;
}
Cow::Cow(const char* nm, double wt) {
int len;
len = strlen(nm);
name = new char[len + 1];
strcpy_s(name,len+1, nm);
weight = wt;
num_cows++;
cout << num_cows << ": \"" << name << "\" created" << endl;
}
void Cow::showCow() {
cout << "Cow name: " << name << ", Cow weight: " << weight << endl;
}
Cow::~Cow() {
cout << "\"" << name << "\" deleted ";
--num_cows;
cout << num_cows << " left" << endl;
delete[] name;
}
void call1(Cow& c) {
cout << "Cow passed by reference:" << endl;
c.showCow();
}
void call2(Cow c) {
cout << "Cow passed by value:" << endl;
c.showCow();
}
call1和call2為兩個測試函數(shù),call1使用引用傳參調(diào)用類對象,call2使用值傳遞。
現(xiàn)在讓我們來測試這個類:
void CowTest() {
Cow Bob("Bob", 120);
Cow Alice("Alice", 150);
Cow Tom("Tom", 120);
cout << "Bob: ";
Bob.showCow();
cout << "Alice: ";
Alice.showCow();
cout << "Tom: ";
Tom.showCow();
cout << endl;
call1(Bob);
cout << "Bob: ";
Bob.showCow();
cout << endl;
call2(Alice);
cout << "Alice: ";
Alice.showCow();
cout << endl;
cout << "Initialize one object to another" << endl;
Cow Aim = Tom;
cout << "Aim: ";
Aim.showCow();
cout << endl;
cout << "Assign one object to another" << endl;
Cow Wu;
Wu = Bob;
cout << "Wu: ";
Wu.showCow();
}

我們可以發(fā)現(xiàn)明顯的兩個問題:
- 在創(chuàng)建Aim時,沒有調(diào)用我們編寫的構(gòu)造函數(shù)和默認構(gòu)造函數(shù)。
- 當調(diào)用call2函數(shù)時出現(xiàn)了一次未知的析構(gòu)函數(shù)調(diào)用,使得再次訪問Alice時出現(xiàn)了亂碼。
出現(xiàn)問題1的原因:
Cow Aim = Tom;
上述句將轉(zhuǎn)化為:
Cow Aim = Cow(Tom);
對應(yīng)的構(gòu)造函數(shù)形式為Cow(const Cow&) -- 這就是復制構(gòu)造函數(shù)。
當我們沒有聲明復制構(gòu)造函數(shù)時,C++將會提供默認的復制構(gòu)造函數(shù)
出現(xiàn)問題2的原因:
當我們使用值傳遞,傳遞Alice對象時,會先調(diào)用復制構(gòu)造函數(shù),創(chuàng)建臨時對象副本,當函數(shù)執(zhí)行完畢后將釋放此副本。
在上述類實現(xiàn)中,我們沒有定義復制構(gòu)造函數(shù),所以這里將使用默認的復制構(gòu)造函數(shù)。默認復制構(gòu)造函數(shù)逐個復制非靜態(tài)成員(成員賦值也叫淺復制),復制的是成員的值。
這里淺復制就出現(xiàn)了問題,臨時對象的name與Alice.name值相同,是一個地址。當call2函數(shù)執(zhí)行完成,釋放臨時對象時,調(diào)用臨時對象的析構(gòu)函數(shù),釋放name指向的內(nèi)存,導致再次查看Alice時出現(xiàn)了亂碼。
所以解決上述兩個問題的方法為創(chuàng)建一個復制構(gòu)造函數(shù),進行深復制。復制構(gòu)造函數(shù)如下:
class Cow
{
private:
static int num_cows;
char* name;
double weight;
public:
...
Cow(const Cow& c);
...
};
Cow::Cow(const Cow& c) {
int len;
len = strlen(c.name);
name = new char[len + 1];
strcpy_s(name, len + 1, c.name);
weight = c.weight;
num_cows++;
cout << num_cows << ": \"" << name << "\" created" << endl;
}
賦值符(=)重載
解決復制構(gòu)造函數(shù)問題后,我們再次對上述類進行測試:

這次類對象的創(chuàng)建與析構(gòu)數(shù)量已經(jīng)對應(yīng)上了,值傳遞也未出現(xiàn)問題。但在最后釋放對象時還是出現(xiàn)了問題。
按照最先創(chuàng)建的對象,最后釋放的原則,可以發(fā)現(xiàn)Wu和Bob釋放了同一塊內(nèi)存導致程序出現(xiàn)錯誤。
產(chǎn)生該問題的原因:
Cow Wu;
Wu = Bob;
C++中類的賦值時通過自動重載賦值符=實現(xiàn)的,出現(xiàn)該問題的原因與默認復制構(gòu)造函數(shù)一樣,使得Wu.name = Bob.name(name為char類型指針)。在調(diào)用析構(gòu)函數(shù)釋時,這兩個對象釋放了同一塊內(nèi)存。
解決此問題需要重載賦值運算符=:
class Cow
{
private:
static int num_cows;
char* name;
double weight;
public:
...
Cow& operator=(const Cow& c);
...
};
Cow& Cow::operator=(const Cow& c) {
int len;
if (this == &c) {
return *this;
}
delete[] name;
len = strlen(c.name);
name = new char[len + 1];
strcpy_s(name, len + 1, c.name);
weight = c.weight;
return *this;
}
