由一份auto_ptr原始碼所引發的思考 (轉)

worldblog發表於2007-12-14
由一份auto_ptr原始碼所引發的思考 (轉)[@more@]

由一份auto_ptr所引發的思考
                               Kyle

  CPPCN 版權所有
如果我問你,auto_ptr最關鍵的地方,或者說它與一般指標最不一樣的地方在哪裡,你一定會說它是一個,它與自己所佔有的資源密切相關,當自己消亡的時候,它也會將自己所擁有的資源一同釋放。很好,它之所以會擁有這一特性,全都要歸於析構的功勞,在解構函式中,它會進行善後處理,這樣也就很好的避免了資源洩漏。當然,引入auto_ptr的原因還不至於這麼簡單,因為許多資源洩漏的問題主要是由於我們的粗心大意所致,所以只要我們細心一些,還是會避免一些不該發生的資源洩漏問題。那究竟是什麼原因讓我們引入auto_ptr呢?對了,你一定也想到了,就是在異常處理的時候。Ok,來看下面這個例子:
void foo()
{
 ClassA *ptr=new ClassA;
 try
 {
/*  此處放置可能丟擲異常的操作 */
 }
 catch(…)
 {
   delete ptr;
   throw;
 }
 delete ptr;
}
上面這個例子使用了一般的指標,你會發現它防止發生資源洩漏的處理是多麼複雜,必須在每個catch中進行資源的釋放,而且有可能會有許多的catch。天哪,這簡直就是災難,它會使我們的程式碼很長,不易維護,如果忘記其中的一個,就會產生莫名其妙的錯誤。既然一般的指標防止資源洩漏會如此的繁瑣,那有沒有一個辦法可以使我們不必操心資源的釋放呢,由於在異常發生的時候,會進行堆疊解退,所以我們不必擔心作為區域性變數的指標本身不被銷燬,既然這樣,我們乾脆建立一個指標物件,好比下面這樣:
template
class auto_ptr1
{
private:
 T* ap;
public:
   ……..
 ~auto_ptr(); //資源釋放
}
當指標被銷燬的時候,必然會解構函式,那就在解構函式中進行資源的釋放不就ok了,呵呵,怎麼樣,是不是很簡單呢?的確,整個邏輯的確很簡單,但是如果我們在深入思考一下這個指標物件的特性的話,我們會發現有一個困難的問題等待我們去解決。那下面就讓我們來看看會遇到什麼困難。
   由於在auto_ptr銷燬的時候它會自動透過解構函式釋放所擁有的資源,那麼也就決定了auto_ptr對於資源的獨佔性,即一個資源只能被一個auto_ptr所指向。這一點應該很好理解,假設有兩個auto_ptr指向同一個資源,那當其中一個被銷燬的時候,另一個將會指向哪裡呢?這種指標往往是最為危險的。既然這樣,我們怎樣才能保證這種獨佔性呢,其實也很簡單,當對指標進行賦值和複製的時候,剝奪原有指標對資源的擁有權,問題也就迎韌而解了。就好比這樣:
 auto_ptr p(new int(20));
 auto_ptr q;
 q=p;  //p已經喪失了對資源的擁有權,q現在是p的主人

對於這種一般性的情況,問題似乎已經解決了,下面讓我們來看一種特殊但卻合理的情況:

auto_ptr foo()
{
 auto_ptr p(new int(20));
 return p;
}
int main()
{
 auto_ptr q(foo());
 return 0;
}
你認為上面這種情況怎麼樣,它是合理的,因為它實現了資源的順利移交,但是你認為auto_ptr q(foo());這一句應該怎樣才能成功呢?為了說清這個問題,還需要說說左值和右值以及臨時物件的問題。也許你會說左值應該就是能夠改變的變數,右值當然就是不能改變的變數嘍!對嗎?對了一點點,實際上左值是能夠被引用的變數,說的通俗點就是有名字的變數,你一定想到了些什麼,對了,臨時變數就沒有名字,即使有你也不會知道,因為它不是由你建立的,會在內部辨別它,但你並不知道,因此臨時變數不是左值,而是右值。你也許還會問,那const變數是不是左值呢?根據定義,它有名字,當然就是左值了。因此左值並非一定“可被修改”。但是左值和右值與引數有什麼關係嗎?我要告訴你的是:有,而且相當密切,因為標準c++規定:若傳遞給型別為引用的形參的實參是右值的話必須保證形參為const引用。Ok,現在讓我們回到原來的問題上,由於foo()按值返回,因此編譯器必然會產生一個臨時物件,也就是說 auto_ptr q(foo()); 這一句中q的建構函式傳入的引數是一個右值,因此若想讓這一句成功的呼叫,它的原型必須是這樣的:auto_ptr(const auto_ptr&); 但是這樣行嗎?顯然是不行的,因為我們還要剝奪原有指標對資源的擁有權呢,如果採用const引用,那是無法進行剝奪的,因為你無法修改它。你也許想到了另一個辦法,我們只要用mutable修飾核心資料域的話,那麼即使它是const也可以進行修改它的核心資料。這個辦法看起來似乎不錯,但如果我們在考慮一下下面這種情況,你或許會改變你的看法,假設有一個const auto_ptr ,如果我們把它賦值給另一個auto_ptr的話,你說應該是怎樣的情況發生,呵呵,當然應該是禁止了,因為你不應該試圖去改變一個const物件,即禁止剝奪一個const auto_ptr對資源的擁有權。但是如果按照你的想法,採用mutable的話,這種改變是可以實現的,因此你應該打消採用mutable的念頭。那麼難道就沒有辦法了嗎?當然有,但是不太容易想到,請看下面這個簡單的例子:

class X
{
private:
 int value;
public:
 X(int v=0)
 {
 value=v;
 }
 X(X& a)
 {
 value=a.value;
 a.value=0;
 }
 int set(int v)
 {
 value=v;
 return value;
 }
 friend ostream& operator << (ostream& os, const X& x)
 {
 os< }
};

X f()
{
 X a(100);
 return a;
}

int main()
{
 X c(f());
 cout< return 0;
}
上面這個例子和我們所遇到的情況有些相似X c(f());這一句是無法呼叫成功的,而且也不能把複製建構函式的引用引數變為const,因為我們要修改引數。Ok,我們就利用這個簡單的例子來解決我們所遇到的問題。既然我們已經想到的一些方案不能達到我們的目的,那我們怎麼做呢,對了,我們可以用型別轉換函式。下面讓我來幫你整理一下思路:
1.我們首先應該先定義一個型別轉換層,它的核心資料應該和X的核心資料一樣,例如:
struct Y
{
 int val;
 Y(int v):val(v){}
};
有了這個轉換層,我們就可以先把函式f()返回時所生成的臨時物件透過一個從X到Y的轉換函式轉型到Y。然後再透過一個從Y到X的轉換函式進行物件的構造。至此,所有問題都得以解決。下面讓我們來看一下具體方法。
2.新增一個從X到Y的型別轉換函式。如下:
operator Y()
{
 Y y(value);
 return y;
}
3.新增一個從Y到X的型別轉換函式,即只有一個引數的建構函式。

X(Y a)
{
 value=a.val;
}
OK,大功告成,你可以把這個例子在你的編譯器上實現,果然能夠解決所有的問題(在VC上會有一點問題,因為VC在臨時物件這一點上對標準C++的支援不夠好,用臨時物件作引數的時候不加const也可以編譯透過),下面我給出auto_ptr的一個實作範例,我想你應該能夠理解它了:)

template
struct auto_ptr_ref
{
 Y* yp;
 auto_ptr_ref(Y* rhs):yp(rhs){}
};  //注意這個轉換層

template
class auto_ptr1
{
private:
 T* ap;
public:
 typedef T element_type;

 explicit auto_ptr1(T* ptr=0) throw():ap(ptr){}

 auto_ptr1(auto_ptr1& rhs) throw():ap(rhs.release()){}

 template
 auto_ptr1(auto_ptr1& rhs) throw():ap(rhs.release()){}

 auto_ptr1& operator = (auto_ptr1& rhs) throw()
 {
   reset(rhs.release());
   return *this;
 }

 template
 auto_ptr1& operator = (auto_ptr1& rhs) throw()
 {
   reset(rhs.release());
   return *this;
 }

 ~auto_ptr1() throw()
 {
   delete ap;
 }

 T* get() const throw()
 {
   return ap;
 }

 T& operator *() const throw()
 {
   return *ap;
 }

 T* operator ->() const throw()
 {
   return ap;
 }

 T* release() throw()
 {
   T* tmp(ap);
   ap=0;
   return tmp;
 }

  void reset(T* ptr=0) throw()
 {
 if(ap!=ptr)
 {
   delete ap;
   ap=ptr;
 }
 }

 auto_ptr1(auto_ptr_ref rhs) throw():ap(rhs.yp){}

 auto_ptr1& operator = (auto_ptr_ref rhs) throw()
 {
   reset(rhs.yp);
   return *this;
 }

  template
 operator auto_ptr_ref() throw()
 {
   return auto_ptr_ref(release());
 }

 template
 operator auto_ptr1() throw()
 {
   return auto_ptr1(release());
 }
};

好了,今天就說到這吧,大家有什麼問題可以提出


來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/10752043/viewspace-993522/,如需轉載,請註明出處,否則將追究法律責任。

相關文章