天天看點

《C++标準程式庫》讀書筆記(三)

 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,任務完成。

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

本文轉自Phinecos(洞庭散人)部落格園部落格,原文連結:http://www.cnblogs.com/phinecos/archive/2008/08/19/1271700.html,如需轉載請自行聯系原作者

繼續閱讀