C 拷貝建構函式詳解

2021-09-12 02:34:51 字數 3843 閱讀 6692

**:

首先對於普通型別的物件來說,它們之間的複製是很簡單的,例如:

int a=100;

int b=a;

而類物件與普通物件不同,類物件內部結構一般較為複雜,存在各種成員變數。

下面看乙個類物件拷貝的簡單例子。

#includeusing namespace std;

class cexample

//拷貝建構函式

cexample(const cexample & c)

//析構函式

~cexample()

void show()

cexample(const cexample & c)

~cexample()

void show()

//拷貝建構函式

cexample(const cexample & c)

//析構函式

~cexample()

void show()

#includeusing namespace std;

class rect

~rect()

static int getcount()

private:

int width;

int height;

static int count;

};int rect::count=0;

int main()

rect(const rect& r)

~rect()

static int getcount()

private:

int width;

int height;

static int count;

};int rect::count=0;

int main()

~rect()

private:

int width;

int height;

int *p;

};int main()

在這段**執行結束之前,會出現乙個執行錯誤。原因就在於在進行物件複製時,對於動態分配的內容沒有進行正確的操作。我們來分析一下:

在執行定義rect1物件後,由於在建構函式中有乙個動態分配的語句,因此執行後的記憶體情況大致如下:

在使用rect1複製rect2時,由於執行的是淺拷貝,只是將成員的值進行賦值,這時 rect1.p = rect2.p,也即這兩個指標指向了堆裡的同乙個空間,如下圖所示:

當然,這不是我們所期望的結果,在銷毀物件時,兩個物件的析構函式將對同乙個記憶體空間釋放兩次,這就是錯誤出現的原因。我們需要的不是兩個p有相同的值,而是兩個p指向的空間有相同的值,解決辦法就是使用「深拷貝」。

在「深拷貝」的情況下,對於物件中動態成員,就不能僅僅簡單地賦值了,而應該重新動態分配空間,如上面的例子就應該按照如下的方式進行處理:

#include#includeusing namespace std;

class rect

rect(const rect& r)

~rect()

private:

int width;

int height;

int *p;

};int main()

此時,在完成物件的複製後,記憶體的乙個大致情況如下:

此時rect1的p和rect2的p各自指向一段記憶體空間,但它們指向的空間具有相同的內容,這就是所謂的「深拷貝」。

通過對物件複製的分析,我們發現物件的複製大多在進行「值傳遞」時發生,這裡有乙個小技巧可以防止按值傳遞——宣告乙個私有拷貝建構函式。甚至不必去定義這個拷貝建構函式,這樣因為拷貝建構函式是私有的,如果使用者試圖按值傳遞或函式返回該類物件,將得到乙個編譯錯誤,從而可以避免按值傳遞或返回物件。

//防止按值傳遞

class cexample

, 如果呼叫foo(obj);  首先class_type obj_local(obj) ,這樣就定義了區域性變數obj_local供函式內部使用

ii)引用傳遞:

無論對內建型別還是類型別,傳遞引用或指標最終都是傳遞的位址值!而位址總是指標型別(屬於簡單型別), 顯然引數傳遞時,按簡單型別的賦值拷貝,而不會有拷貝建構函式的呼叫(對於類型別).

如果不顯式宣告拷貝建構函式的時候,編譯器也會生成乙個預設的拷貝建構函式,而且在一般的情況下執行的也很好。但是在遇到類有指標資料成員時就出現問題 了:因為預設的拷貝建構函式是按成員拷貝構造,這導致了兩個不同的指標(如ptr1=ptr2)指向了相同的記憶體。當乙個例項銷毀時,呼叫析構函式 free(ptr1)釋放了這段記憶體,那麼剩下的乙個例項的指標ptr2就無效了,在被銷毀的時候free(ptr2)就會出現錯誤了, 這相當於重複釋放一塊記憶體兩次。這種情況必須顯式宣告並實現自己的拷貝建構函式,來為新的例項的指標分配新的記憶體。

問題1和2回答了為什麼拷貝建構函式使用值傳遞會產生無限遞迴呼叫的問題;

問題3回答了回答了在類中有指標資料成員時,拷貝建構函式使用值傳遞等於白顯式定義了拷貝建構函式,因為預設的拷貝建構函式就是這麼幹的。

解答:這個問題是在網上見的,當時一下子有點暈。其時從名子我們就知道拷貝建構函式其時就是乙個特殊的建構函式,操作的還是自己類的成員變數,所以不受private的限制。

x::x(const x&); //拷貝建構函式

x::x(x);

x::x(x&, int a=1); //拷貝建構函式

x::x(x&, int a=1, int b=2); //拷貝建構函式

解答:對於乙個類x, 如果乙個建構函式的第乙個引數是下列之一:

a) x&

b) const x&

c) volatile x&

d) const volatile x&

且沒有其他引數或其他引數都有預設值,那麼這個函式是拷貝建構函式.

解答:類中可以存在超過乙個拷貝建構函式。

class x ;
注意,如果乙個類中只存在乙個引數為 x& 的拷貝建構函式,那麼就不能使用const x或volatile x的物件實行拷貝初始化.

如果乙個類中沒有定義拷貝建構函式,那麼編譯器會自動產生乙個預設的拷貝建構函式。

這個預設的引數可能為 x::x(const x&)或 x::x(x&),由編譯器根據上下文決定選擇哪乙個。

q1:建構函式能否過載,析構函式能否過載,為什麼?

a1:建構函式可以,析構函式不可以。

q2:析構函式為什麼一般情況下要宣告為虛函式?

a2:虛函式是實現多型的基礎,當我們通過基類的指標是析構子類物件時候,如果不定義成虛函式,那只呼叫基類的析構函式,子類的析構函式將不會被呼叫。如       果定義為虛函式,則子類父類的析構函式都會被呼叫。

q3:什麼情況下必須定義拷貝建構函式?

a3:當類的物件用於函式值傳遞時(值引數,返回類物件),拷貝建構函式會被呼叫。如果物件複製並非簡單的值拷貝,那就必須定義拷貝建構函式。例如大的堆       棧資料拷貝。如果定義了拷貝建構函式,那也必須過載賦值操作符。

參考部落格:

C 拷貝建構函式詳解

一.什麼是拷貝建構函式 首先對於普通型別的物件來說,它們之間的複製是很簡單的,例如 int a 100 int b a 而類物件與普通物件不同,類物件內部結構一般較為複雜,存在各種成員變數。下面看乙個類物件拷貝的簡單例子。include using namespace std class cexam...

C 拷貝建構函式詳解

一.什麼是拷貝建構函式 首先對於普通型別的物件來說,它們之間的複製是很簡單的,例如 int a 100 int b a 而類物件與普通物件不同,類物件內部結構一般較為複雜,存在各種成員變數。下面看乙個類物件拷貝的簡單例子。include using namespace std class cexam...

C 拷貝建構函式詳解

一.什麼是拷貝建構函式 首先對於普通型別的物件來說,它們之間的複製是很簡單的,例如 而類物件與普通物件不同,類物件內部結構一般較為複雜,存在各種成員變數。下面看乙個類物件拷貝的簡單例子。執行程式,螢幕輸出100。從以上 的執行結果可以看出,系統為物件 b 分配了記憶體並完成了與 物件 a 的複製過程...