C++里面設(shè)計(jì)一個(gè)含指針的類比設(shè)計(jì)一個(gè)不含指針的類要復(fù)雜很多,因?yàn)樾枰紤]Big Three,也就是三個(gè)特殊函數(shù): 拷貝構(gòu)造函數(shù)(copy constructor), 拷貝賦值函數(shù)(copy assignment Operator) 析構(gòu)函數(shù)(destructor)
為什么不含指針的類不需要考慮Big Three呢?因?yàn)镃++編譯器會(huì)生成缺省拷貝構(gòu)造函數(shù)和缺省拷貝賦值函數(shù),這些函數(shù)采用memberwise copy,也就是淺拷貝。如果類不含指針的話,這也就夠用了。編譯器也會(huì)生成缺省的析構(gòu)函數(shù),但該析構(gòu)函數(shù)不做任何操作。如果類不含指針,當(dāng)對(duì)象生命期結(jié)束時(shí)析構(gòu)也不用做什么事情,所以也沒問題。
但是類里面含有指針的話,C++編譯器生成的缺省拷貝構(gòu)造函數(shù)和缺省拷貝賦值函數(shù)會(huì)采用淺拷貝將一個(gè)object的成員一項(xiàng)一項(xiàng)的拷貝給另一個(gè)object,這樣會(huì)導(dǎo)致同一個(gè)類的兩個(gè)object里面的指針指向同一個(gè)地址。這樣,如果object1的指針值賦值給了object2,那object2的指針原來(lái)指向的內(nèi)存就沒人管了,造成內(nèi)存泄漏。那如果object2的指針之前是NULL行不行呢?也不行。因?yàn)槿绻鹢bject 1修改了這個(gè)指針的內(nèi)容,或者刪除了這個(gè)指針,object 2就躺著中槍了。
所以,類里面有指針的話,我們要自己設(shè)計(jì)Big Three。其中的 拷貝構(gòu)造函數(shù)和拷貝賦值函數(shù)會(huì)生成新的指針,然后把指針指向的內(nèi)容拷貝過來(lái)。這就是深拷貝。 以class string為例,代碼如下: class String { public: String(const char* cstr = 0); //構(gòu)造函數(shù) String(const String& str); //拷貝構(gòu)造函數(shù) String& operator=(const String& str); //拷貝賦值函數(shù) ~String(); //析構(gòu)函數(shù) char* get_c_str() const {return m_data;} PRivate: char* m_data; }
先討論拷貝構(gòu)造函數(shù)(copy ctor),代碼例子如下:
inline String::String(const String& str) { m_data = new char[ strlen(str.m_data)+1]; strcpy(m_data, str.m_data); }
注意,拷貝構(gòu)造函數(shù)里m_data是新創(chuàng)建的指針,它指向的內(nèi)容是拷貝過來(lái)的。
拷貝構(gòu)造函數(shù)的用法如下: String s1(“hello”); String s2(s1); // line 1 String s2=s1; // line 2 上面的line 1和line 2都會(huì)調(diào)用拷貝構(gòu)造函數(shù)將s1的字符串賦給s2。注意s2的m_data跟s1的m_data的值不同,但指向的內(nèi)容是一樣的。
下面來(lái)談?wù)?strong>拷貝賦值函數(shù)(copy assignment operator),代碼例子如下: inline String& String::operator=(const String& str) { if (this==&str) return *this; delete[] m_data; m_data=new char[ strlen(str.m_data)+1]; strcpy(m_data, str.m_data); return *this; }
這里有很多地方需要注意: 1) 拷貝賦值函數(shù)必須要檢查this指針是否已經(jīng)是準(zhǔn)備拷貝的對(duì)象里面的指針,換句話說(shuō)就是不能把obj1賦值給obj1自己。因?yàn)槿绻粰z查的話,接下來(lái)的delete[]就把這個(gè)指針指向的內(nèi)存給釋放了,這個(gè)指針就成了野指針。 再細(xì)想一下,為什么前面的拷貝賦值函數(shù)不需要檢查this指針呢?這是因?yàn)橛每截愘x值函數(shù)的時(shí)候,對(duì)象還沒有創(chuàng)建,所以this指針為空。
2) 為什么要delete[] m_data呢? 因?yàn)檫@里s2已經(jīng)存在,它的m_data已經(jīng)指向一段內(nèi)存,里面已經(jīng)有東西。直接把s1的m_data指向的字符串的內(nèi)容拷貝給s2的m_data指向的內(nèi)存是危險(xiǎn)的,一是后者長(zhǎng)度很可能不一樣,再一個(gè)可能會(huì)覆蓋了什么重要東西。所以我們要先把m_data指向的內(nèi)容清除掉, 再重新創(chuàng)建m_data指針。
3) 還有一個(gè)問題,為什么要用delete[]而不是delete呢?其實(shí)這里是可以用delete的,因?yàn)閏har是基本數(shù)據(jù)類型,不涉及到析構(gòu)函數(shù)。delete[]和delete都是把m_data指向的那個(gè)字符串的內(nèi)容給釋放了,效果是一樣的。但是為了規(guī)范起見,new[]應(yīng)該和delete[]對(duì)應(yīng)。
拷貝賦值函數(shù)的用法如下: String s1(“hello”); String s2(s1); s2=s1; //line 3 這里s2會(huì)拷貝s1的字符串內(nèi)容,注意s2和s1的m_data指向不同的地址。這里要特別注意的是: s2=s1 和上面的line2 String s2=s1; 不一樣,后者是調(diào)用的拷貝構(gòu)造函數(shù)。在line3里面,s2這個(gè)object已經(jīng)創(chuàng)建,里面的m_data指向的字符串已經(jīng)有內(nèi)容,而在line2里面,s2還沒有被創(chuàng)建。
下面來(lái)談?wù)?strong>析構(gòu)函數(shù)(dtor)。析構(gòu)函數(shù)在以下三個(gè)地方會(huì)被用到。 1.object生命周期結(jié)束,被銷毀時(shí); 2.delete指向object的指針,或delete指向object的基類類型指針并且基類析構(gòu)函數(shù)是虛函數(shù)時(shí); 3.object 1是object 2的成員,object 2的析構(gòu)函數(shù)被調(diào)用時(shí),object 1的析構(gòu)函數(shù)也被調(diào)用。
String類的析構(gòu)函數(shù)的例子如下: inline String::~String() { delete[] m_data; } 這里析構(gòu)函數(shù)把m_data指向的字符串的內(nèi)存給釋放了。注意這里因?yàn)閏har是基本類型,所以用delete m_data其實(shí)是可以的。但是為了規(guī)范,new[]和delete[]必須要對(duì)應(yīng)。另外,delete[]或者delete怎么知道要釋放多少內(nèi)存呢?因?yàn)閚ew[]的時(shí)候編譯器已經(jīng)知道長(zhǎng)度了。
下面談?wù)刵ew/delete和內(nèi)存以及構(gòu)造函數(shù)和析構(gòu)函數(shù)的關(guān)系: new: 先分配內(nèi)存(這里會(huì)調(diào)用malloc),再調(diào)用ctor delete: 先調(diào)用dtor,再釋放內(nèi)存(這里會(huì)調(diào)用free) 要注意的是,new[]一定要搭配delete[]。這里需要注意的是,對(duì)于C++的基本數(shù)據(jù)類型比如int,char的數(shù)組,因?yàn)椴簧婕拔鰳?gòu)操作(內(nèi)部無(wú)指針),delete和delete[]效果是一樣的,都是釋放內(nèi)存。但是對(duì)于一個(gè)class的object數(shù)組,delete和delete[]效果就不一樣了,舉例如下: String *p=new String[3];
delete[] p; 會(huì)調(diào)用String的析構(gòu)函數(shù)三次,分別析構(gòu)p[0],p[1],p[2],然后釋放掉p[]的所有內(nèi)存。
delete p; 只會(huì)調(diào)用String的析構(gòu)函數(shù)一次,析構(gòu)p[0],然后釋放掉p[]的所有內(nèi)存。那么這里問題就來(lái)了,p[1],p[2]都清掉了,p[1],p[2]的m_data所指向的內(nèi)存也就沒人管了,造成2處內(nèi)存泄漏。
關(guān)于delete p或delete[] p還要注意的一點(diǎn)是它只釋放指針指向的內(nèi)存,至于指針的值它不會(huì)置為NULL。所以有時(shí)候?yàn)榱税踩鹨姡€要把p設(shè)置為NULL。
另外,關(guān)于析構(gòu)函數(shù)還需要注意一點(diǎn),Base class的析構(gòu)函數(shù)一定要是虛函數(shù)。在Effective C++的條款14指出,當(dāng)由基類指針刪除一個(gè)派生類對(duì)象是,如果基類的析構(gòu)函數(shù)非虛,結(jié)果為未定義。舉例來(lái)說(shuō),假如Base的析構(gòu)函數(shù)非虛的話: Base *p = new Derived(); // 此處會(huì)先調(diào)用基類的構(gòu)造函數(shù),然后調(diào)用子類的構(gòu)造函數(shù) delete p; //注意,此處只會(huì)調(diào)用基類的析構(gòu)函數(shù)。
新聞熱點(diǎn)
疑難解答
圖片精選
網(wǎng)友關(guān)注