原文:http://blog.csdn.net/huntrose/archive/2008/11/18/3326388.aspx
指針 是C++中不得不談的一個(gè)話題,或許我還不是很能熟練的掌握指針以及我所要討論的引用計(jì)數(shù)型指針的全部,但是還是有那么些迫不及待想要表達(dá)一下。
指針 pointer 是 資源泄漏 resource leak 的根源(當(dāng)然可能還有其他一些什么東西,在我的映像中 異常 仿佛也會(huì)造成資源泄漏)
最簡單的一個(gè)資源泄漏的例子就是new和delete這樣的動(dòng)態(tài)內(nèi)存分配算子沒有正確使用造成的:
A() { printf("A Constructor!"); }
~A() { printf("A Destructor!"); }
};
void area()
{
A *p = new A();
}
執(zhí)行完 area() 后,自然是只有A構(gòu)造的消息,而A的析構(gòu)卻不見影蹤。這里我們在離開了area作用域后,我們就無法對p所指向之資源進(jìn)行操作,A的實(shí)例就會(huì)被懸掛在內(nèi)存的某處得不到清理。一個(gè)形象點(diǎn)的比方就像人類發(fā)送的宇宙衛(wèi)星失去了動(dòng)力以及和地球的聯(lián)系,無法收回,就變成了宇宙垃圾~
然而利用對象來管理資源是一個(gè)很好的辦法,因?yàn)閷ο蟮膶?shí)例本身在脫離作用域后會(huì)自動(dòng)清理,就像這樣
public:
expilict A_holder(A* p = NULL)
:ptr(p) {}
~A_holder()
{
if (ptr)
delete ptr;
}
private:
A* ptr;
};
如此,我們在area里面把資源的管理權(quán)力交給A_holder,就像下面這樣
{
A_holder ah(new A);
}
這樣,ah在離開area后會(huì)自動(dòng)調(diào)用其析構(gòu)函數(shù),就達(dá)到了自動(dòng)管理該資源的目的。
利用C++的類的實(shí)例離開作用域會(huì)自動(dòng)調(diào)用其析構(gòu)函數(shù)的機(jī)制,可以比較方便的管理資源,但是在使用普通指針的情況下會(huì)存在多個(gè)指針指向同一對象的情況。
{
int a;
int *p1,*p2;
p1 = &a;
p2 = &a;
}
實(shí)際的指針指向情況應(yīng)該是這樣
p1 -à a ß- p2
這里就出現(xiàn)了一個(gè)問題,我們想取消p1的時(shí)候可能會(huì)出現(xiàn)兩種語義:
1、 將p1和其指向的對象一起刪除,這樣p2也就不可以繼續(xù)對a進(jìn)行使用。但是往往p2的使用者不會(huì)知道a已經(jīng)刪除,則出現(xiàn)了錯(cuò)誤。
2、 將p1與其指向的對象解除關(guān)系,這樣p2還可以對a進(jìn)行使用。
對于普通的delete操作,實(shí)現(xiàn)的是第一種情況,這樣通過p2對a進(jìn)行訪問必然會(huì)造成致命的錯(cuò)誤。
在實(shí)現(xiàn)holder類的時(shí)候應(yīng)該也考慮到第二種情況,如果有另外一個(gè)holder也指向這個(gè)資源,其中一個(gè)holder銷毀,另外一個(gè)holder還可能會(huì)使用到它們共同指向的那個(gè)資源。于是,holder的作用就不僅僅是單單的持有和施放資源,還應(yīng)該處理有多少個(gè)對其hold資源的引用(即引用計(jì)數(shù)),并且在引用計(jì)數(shù)降為0時(shí)真正的銷毀資源實(shí)體。
如此,一個(gè)行為類似指針(有->,*操作符)的智能指針出現(xiàn),它管理賦予其資源的引用計(jì)數(shù),也管理該資源的生死存亡。
一個(gè)簡單的Reference Count Smart Pointer的實(shí)現(xiàn)如下:
#ifndef COUNTED_PTR_HPP
#define COUNTED_PTR_HPP
/*class for counted reference semantics
*-deletes the object to which it refers when the last CountedPtr
* that refers to it is destroyed
*/
template <class T>
class CountedPtr {
private:
T* ptr; // pointer to the value
long* count; // shared number of owners
public:
//initialize pointer with existing pointer
//-requires that the pointer p is a return value of new
explicit CountedPtr (T* p=0)
: ptr(p), count(new long(1)) {
}
//copy pointer (one more owner)
CountedPtr (const CountedPtr<T>& p) throw()
: ptr(p.ptr), count(p.count) {
++*count;
}
//destructor (delete value if this was the last owner)
~CountedPtr () throw() {
dispose();
}
//assignment (unshare old and share new value)
CountedPtr<T>& operator= (const CountedPtr<T>& p) throw() {
if (this != &p) {
dispose();
ptr = p.ptr;
count = p.count;
++*count;
}
return *this;
}
//access the value to which the pointer refers
T& operator*() const throw() {
return *ptr;
}
T* operator->() const throw() {
return ptr;
}
private:
void dispose() {
if (--*count == 0) {
delete count;
delete ptr;
}
}
};
#endif /*COUNTED_PTR_HPP*/
由此,一個(gè)新的問題出現(xiàn)了!循環(huán)引用!
這樣的一個(gè)引用計(jì)數(shù)型智能指針目的是為了防止資源泄漏,但是只需要一個(gè)很小巧的代碼就可以讓這樣的初衷化為烏有……
class A
{
public:
A() {cout<<"A CON"<<endl;}
~A() {cout<<"A DES"<<endl;}
void hold(CountedPtr<A> ptr)
{
m_ptr = ptr;
}
private:
CountedPtr<A> m_ptr;
};
void self_cir_area()
{
CountedPtr<A> pA(new A());
pA->hold(pA);
}
可以看見,一個(gè)對象A中有一個(gè)引用計(jì)數(shù)型智能指針,這樣的設(shè)計(jì)可能會(huì)很常見(指向自身類型的結(jié)構(gòu)體——鏈表)
但是,當(dāng)自身循環(huán)引用發(fā)生的時(shí)候會(huì)怎么樣呢? 下面就來看看這么兩句代碼
這里我們新建一個(gè)資源,并且把這個(gè)資源的管理權(quán)移交給pA這個(gè)引用計(jì)數(shù)型智能指針對象管理。如此,pA中的引用計(jì)數(shù)被初始化為1。
這里,我們把pA對象傳入給實(shí)例化的A對象中的引用計(jì)數(shù)型智能指針m_ptr,m_ptr執(zhí)行這樣的一個(gè)成員函數(shù):
//assignment (unshare old and share new value)
CountedPtr<T>& operator= (const CountedPtr<T>& p) throw() {
if (this != &p) {
dispose();
ptr = p.ptr;
count = p.count;
++*count;
}
return *this;
}
因?yàn)檫@里很明顯不是自身賦值,A中的m_ptr和pA不是同一個(gè)對象,所以進(jìn)入if結(jié)構(gòu)中調(diào)用下面的內(nèi)容。dispose是用作清理,因?yàn)閙_ptr并沒有指向任何東西,所以第一個(gè)函數(shù)并沒有真正的意義。
然后
m_ptr.count = pA.count;
++(*m_ptr.count); //(*pA.count)也被++
到此,pA的引用計(jì)數(shù)為2
嗯,下面就pA這個(gè)對象理所當(dāng)然的離開了作用域,調(diào)用其析構(gòu)函數(shù):
~CountedPtr () throw() {
dispose();
}
噢,是一個(gè)轉(zhuǎn)向,調(diào)用其private成員函數(shù)dispose():
if (--*count == 0) {
delete count;
delete ptr;
}
}
很簡單,將引用計(jì)數(shù)-1,由2變成1,不為0,所以if結(jié)構(gòu)內(nèi)的語句不被執(zhí)行。
由此,我們又制造了一個(gè)完美的太空垃圾……
這樣的循環(huán)引用問題應(yīng)該是在設(shè)計(jì)的過程中就應(yīng)該避免的,如果用UML語言描述
A中持有一個(gè) 引用計(jì)數(shù)型智能指針 的語義就是 這個(gè) 持有關(guān)系 是需要在 A消失的時(shí)候所持有的對象也隨之消失(這正是智能指針的作用,在脫離作用域自動(dòng)清除其持有的資源)。如此就構(gòu)成了 組合 關(guān)系。如果要表示 聚合 關(guān)系,即有 部分-整體 關(guān)系但是部分不隨整體的消失而消失,這就不是 智能指針 所表達(dá)的語義。
還有可能遇見的循環(huán)引用就是 A1 持有 A2, A2 持有 A1 的情況……
這樣A1,A2中對雙方的引用計(jì)數(shù)都是2,當(dāng)一方“銷毀”的時(shí)候,雙方的應(yīng)用計(jì)數(shù)都變?yōu)?,實(shí)際上并沒有銷毀任何東西,制造了兩個(gè)完美無暇的太空垃圾~
這里又引發(fā)出一個(gè)問題,這樣的資源泄漏問題實(shí)際上還是由程序員自身引起的。
C++之所以是一個(gè)很容易出錯(cuò)的語言,很大一部分在于其資源的管理權(quán)力全權(quán)交給了程序員。這樣的權(quán)力到底是造福了程序員還是迷惑了程序員呢?
這里我卻想起了蜘蛛俠中的一句名言: “一個(gè)人能力有多大,責(zé)任就有多大!”
對C++中指針的指責(zé)不是一天兩天了,其易錯(cuò)性無可厚非,但是它卻給了你其他語言無法給你的能力!這就是我的觀點(diǎn),你能力有這么大,你就有責(zé)任來治理好這些資源。而非一再推卸責(zé)任。如果真的是要推卸責(zé)任,也就應(yīng)該去選擇其他那些剝奪你的能力而減少你的責(zé)任的語言,因?yàn)槟阌羞x擇權(quán)!就像說英語和中文一樣,并沒有哪個(gè)人在強(qiáng)迫你,不是么?熱愛C++是一種態(tài)度,對一個(gè)語言的利弊都了然于心,了解其可以做什么不可以做什么,怎樣才可以更好的使用它來做什么,才能更好的使用它。更何況,there are rarely things that are not possible in C++
本文來自CSDN博客,轉(zhuǎn)載請標(biāo)明出處:http://blog.csdn.net/huntrose/archive/2008/11/18/3326388.aspx