個人見解,先談談淺拷貝與深拷貝之間的區別,區分它們的最大區別就是在調用完拷貝構造函數後,兩個對象之間是否還存在一定的聯系,如果兩個對象能夠完全獨立,則說明是深拷貝,否則是淺拷貝。以各種教材的String類的深拷貝實現為例,下面進行說明。
為了實現深拷貝操作,我們需要自己來構建拷貝構造函數,因為我們一旦構建了自己的拷貝構造函數,系統就不再提供默認的拷貝構造函數了。
下面是String.h
#ifndef _STRING_H_ #define _STRING_H_ class String { public: String(char *str=""); //帶一個參數的構造函數,可傳入參數如“ABC” ~String(); void Display(); String( const String &other); //拷貝構造函數 String & operator=(const String &other); //等號賦值運算符重載 private: char *str_; //String需要維護str_數據成員 char *AllocAndCpy(char *str); }; #endif
下面是String類的具體實現,String.cpp
#include "String.h" #include上面代碼中的AllocAndCpy函數是非常重要的函數,它重新分配了一塊內存空間,然後將原內存的數據復制至新內存空間中,這樣兩個類對象之間就有各自獨立的內存空間,對象之間就沒有聯系,這就是深拷貝。如果不這麼做,直接通過str_ = other.str_實施拷貝,只是簡單的復制指針的地址,這樣兩個類對象實際指向的是同一塊內存,當兩上對象的生命周期結束後,均需要釋放內存空間,這樣就造成了同一塊內存單元被釋放了兩次。因為它們僅僅拷貝了“形”,而沒有拷貝“本質”的數據,所以拷貝構造函數需要我們自己來重新編寫。由於等號賦值重載函數需要賦值操作,實際上也是復制,所以也需要自己編寫operator=函數。#include using namespace std; char *String::AllocAndCpy(char *str) //實施深拷貝的函數 { int len = strlen(str) + 1; char *tmp = new char[len]; memset( tmp, 0, len); strncpy(tmp, str, len ); return tmp; } String::String( const String & other) /* : str_(other.str_) 這種方式還是淺拷貝*/ { str_ = AllocAndCpy(other.str_); //函數內的實現才是真正的深拷貝,它的處理使得str_與原對象脫離了聯系 } String & String::operator=(const String &other) { if( this == &other) { return *this; } //銷毀原有空間 delete [] str_; str_ = AllocAndCpy(other.str_); return *this; } String::String( char *str) { str_ = AllocAndCpy(str); } String::~String() { delete [] str_; } void String::Display() { cout << str_ << endl; }
下面是測試代碼,代碼中也有詳細的注釋:
int main(void) { String s1("AAA"); s1.Display(); String s2 = s1;//調用默認的拷貝構造函數,系統提供的默認拷貝構造函數實施的是淺拷貝s2.str_ = s1.str_;相同於s1,s2兩個對象的str_指針指向相同的內存,當兩個對象生存期結束時,都要調用析構函數,導致同一塊內存被,釋放了兩次,故而產生錯誤.解決方法實施深拷貝,自己提供拷貝構造函數 //等號運算符的重載 String s3; s3.Display(); s3 = s2; // = 號運算符,它調用的是系統默認的等號運算符,實施的也是淺拷貝,s3.str_ = s2.str_;仍然會出現同一塊內存被銷毀兩次的情況,所以要自己提供等號運算符實施深拷貝。等價於s3.operator=(s2); return 0; }
下面說明關於禁止拷貝的情形。在某些場合,比如設計模式中有個Singleton單例模式,即一個類只能有一個實例,就是說這個類的對象是要獨一無二的,它不允許被拷貝,也不允許賦值,也就是說我們要提供禁止拷貝的功能,以下就是將一個類實施為禁止拷貝的步驟,其實很簡單:
(1)將拷貝構造函數與operator=(等號運算符重載函數)均聲明為private私有訪問權限
(2)在CPP文件中不提供他們的實現
這樣,當在主程序進行拷貝復制或賦值操作時,編譯將出錯,因為它們沒有拷貝構造函數或賦值操作的實現。只有聲明,而且還是私有的。
下面總結一下,空類默認的成員函數有6個:
class Empty{}; //這是一個空類。 Empty(); //默認構造函數 Empty(const Empty &);//默認拷貝構造 ~Empty(); //默認析構函數 Empty & operator=(const Empty &)//默認賦值運算符 Empty *operator&();//取地址運算符 const Empty *operator &() const; //取地址運算符const下面舉例說明operator&()與operator &() const的用法:
#includeusing namespace std; class Empty { public: Empty * operator&() { cout << "AAA"<< endl; return *this; } const Empty *operator&() const { cout << "BBB"< 空類的大小是1個字節,編譯器將為它生成一個字生的空間 cout << sizoef(Empty) << endl; return 0; }