C++回顧之深淺拷貝、禁止拷貝、空類的預設成員
阿新 • • 發佈:2019-01-24
個人見解,先談談淺拷貝與深拷貝之間的區別,區分它們的最大區別就是在呼叫完拷貝建構函式後,兩個物件之間是否還存在一定的聯絡,如果兩個物件能夠完全獨立,則說明是深拷貝,否則是淺拷貝。以各種教材的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
上面程式碼中的AllocAndCpy函式是非常重要的函式,它重新分配了一塊記憶體空間,然後將原記憶體的資料複製至新記憶體空間中,這樣兩個類物件之間就有各自獨立的記憶體空間,物件之間就沒有聯絡,這就是深拷貝。如果不這麼做,直接通過str_ = other.str_實施拷貝,只是簡單的複製指標的地址,這樣兩個類物件實際指向的是同一塊記憶體,當兩上物件的生命週期結束後,均需要釋放記憶體空間,這樣就造成了同一塊記憶體單元被釋放了兩次。因為它們僅僅拷貝了“形”,而沒有拷貝“本質”的資料,所以拷貝建構函式需要我們自己來重新編寫。由於等號賦值過載函式需要賦值操作,實際上也是複製,所以也需要自己編寫operator=函式。#include "String.h" #include <cstring> #include <iostream> 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的用法:
#include <iostream>
using namespace std;
class Empty
{
public:
Empty * operator&()
{
cout << "AAA"<< endl;
return *this;
}
const Empty *operator&() const
{
cout << "BBB"<<endl;
return this;
}
};
int main()
{
Empty e;
Empty *p = &e; //將呼叫取地址運算子,等價於e.operator&()
const Empty e2;
const Empty *p2 = &e2; //呼叫的是含const的取地址運算子
//空類的大小是1個位元組,編譯器將為它生成一個字生的空間
cout << sizoef(Empty) << endl;
return 0;
}