auto_ptr與auto_ptr_ref

今天我在看 The C++ Standard Library 的時候,總覺的上面講的關於auto_ptr_ref的問題沒有說清楚,查了網上的資料發現也沒有說清。 也許還有很多朋友像我一樣對此存在疑問。其實,這個問題有沒有弄清楚,對實際編程影像並不大,但是本着“勿在浮沙築高臺”的精神,我用了一個晚上的時間,來個徹底的大調查,終於基本上弄明白了其中的道理。(大多數時間都浪費在VC上,因爲 VC支持對右值的非 常應用,而標準C++不可以,所以在VC中,有沒有auto_ptr_ref實際上都無所謂!)

STL中有一個智能指針auto_ptr,可以實現簡單的內存自動回收,防治內存泄漏(memory leakage)。auto_ptr實際是一個類,在該類析構時自動調用delete,從而打到了內存回收的效果。

但是,由於同一個指針同一時刻只能被一個auto_ptr佔用,如果採用賦值操作(=)或者拷貝構造函數調用等,就會發生所有權轉移,例如:

auto_ptr<int> p(new int(0));

auto_ptr<int> q;

此時,p擁有指向一個int的指針,q的指針爲空。如果執行

q=p;

則,p指向空,q指向int;

但是,這樣所有權轉換的問題同樣發生在參數傳遞中,例如

void foo(auto_ptr<int> t);

如果調用 foo(p);

那麼p就丟失了指針,所以一個解決方法是用引用

例如 void foo1(auto_ptr<int>& t);

         void foo2 (const auto_ptr<int>& t);

兩者都是可以的,不過foo1非常不安全,因爲在函數裏面很容易通過類似賦值的操作使t丟失指針,而foo2不會。例如

void foo2(const auto_ptr<int>& t)

{ auto_ptr<int> m; m=t;}

會發生編譯錯誤,從而避免災難的發生。

但隨之又出現一個很大的問題,就是auto_ptr類的拷貝構造函數,或者賦值函數。

最理想的情況是這樣(如果能成功,就不會有別的什麼問題):

auto_ptr(const auto_ptr& rhs):ap(rhs.release()){}

但由於上述的原因,會發生編譯錯誤(因爲調用了release(),而release()會改變成員變量,不再是const)。

所以只能去掉const,變爲

auto_ptr(auto_ptr& rhs):ap(rhs.release()){}

這樣可以編譯成功,而且往往也能正確運行,但是唯一的問題是: 當rhs爲右值時會出現問題。

爲了簡化問題,先假設拷貝構造函數什麼都不做,即:

auto_ptr(auto_ptr& rhs){}

那麼,如果有 auto_ptr<int> p(new int(10))

執行 auto_ptr<int> q(p),不會有任何問題,因爲p是左值。

但如果執行      auto_ptr<int> q(auto_ptr<int>(new int(10))) ,則會發生編譯錯誤,因爲auto_ptr<int>(new int(10)) 是右值,對右值的引用只能是常引用,也就是"const auto_ptr& rhs"的形式。但這裏要注意的是,剛纔那段代碼用VC編譯沒有任何問題,並且可以順利運行,但是用GCC之類的標準c++就不能順利編譯。

在VC中

auto_ptr<int>& p=auto_ptr<int>(new int(0))     是合法的,但在標準C++中是不合法的,只有

const auto_ptr<int>& p=auto_ptr<int>(new int(0)) 纔是合法的,也即在標準C++中,對右值的引用只能是常引用。

所以說,要在標準C++中實現 auto_ptr<int> p(auto_ptr<int>(new int(0))) 就變得不可能了,因爲如上所說,拷貝構造函數是這樣的形式:

auto_ptr(auto_ptr<T>& rhs):ap(rhs.release()){}

但是不能把右值傳到一個非 常引用中。

但畢竟有聰明的人能想到解決辦法,利用代理類( proxy class)

聲明如下結構,爲了方便,我用int代替模板參數

struct auto_ptr_ref

{

   int* p;

   auto_ptr_ref(int *t):p(t){}

};

然後在auto_ptr類中增加了以下東東:

auto_ptr(auto_ptr_ref rhs):ap(rhs.p){}

auto_ptr& operator=(auto_ptr_ref rhs){reset(rhs.p); return *this;}

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

之後,如果在標準C++有以下調用(VC中也會按照這個步驟調用,雖然沒有auto_ptr_ref它也能直接調用)

auto_ptr<int> p(auto_ptr<int>(new int(0)))

便可以成功,過程如下:

1. 構造臨時對象 auto_ptr<int>(new int(0))

2. 想將臨時對象通過拷貝構造函數傳給p,卻發現沒有合適的拷貝構造函數,因爲只有auto_ptr(auto_ptr& rhs)這個不能用,又沒有auto_ptr(const auto_ptr& rhs) (因爲用了在所有權轉移中會出錯),呵呵!

3. 編譯器只能曲線救國,看看類型轉換後能不能傳遞。

4. 由於我們定義了 operator auto_ptr_ref() 所以編譯器自然就可以試一下轉爲 auto_ptr_ref類型。

5. 編譯器猛然間發現,我們定義了 auto_ptr(auto_ptr_ref rhs):ap(rhs.p){} 的構造函數,可以傳遞。

6. 順利構造p,任務完成。

其實說白了問題很簡單,因爲構造函數不能接受右值,則取 中間左值=右值, 然後再讓函數接受中間左值。 而這一系列過程正是利用編譯器能夠自動進行類型轉換而完成的。


本文來自CSDN博客,轉載請標明出處:http://blog.csdn.net/benny5609/archive/2008/06/06/2516822.aspx

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章