這幾天開始拜讀侯捷先生和孟巖先生的譯作《C++標準程序庫:自修教程與參考手冊》 。兩位先生確實譯功上乘,讀得很順。但是讀到P55頁關于auto_ptr_ref的討論,卻百思不得其解:為什么需要引入auto_ptr_ref這個輔助類呢?
從書中描述來看,仿佛與拷貝構造函數 、右值 、類型轉換 有關。于是,結合auto_ptr的源代碼,google之、baidu之,找了一推資料,終于初步 搞清該問題。
auto_ptr的擁有權
C++常見的智能指針有std::auto_ptr、boost::shared_ptr、boost::scoped_ptr、boost::shared_array、boost::scoped_array等。auto_ptr只是其中一種而已。但是,為什么auto_ptr才有auto_ptr_ref ,而boost::shared_ptr卻沒有shared_ptr_ref呢?
答案與auto_ptr的特性有關。auto_ptr強調對資源的擁有權 (ownership)。也就是說,auto_ptr是"它所指對象"的擁有者。而一個對象只能屬于一個擁有者,嚴禁一物二主,否則就是重婚罪,意料外的災難將隨之而來。
為了保證auto_ptr的擁有權唯一,auto_ptr的拷貝構造函數和賦值操作符做了這樣一件事情:移除另一個auto_ptr的擁有權 。為了說明擁有權的轉移 ,請看下面的代碼示例:
- #include <iostream>
- #include <memory>
- using namespace std;
-
- int main(int argc, char **argv){
- auto_ptr<int> ptr1(new int(1));
- auto_ptr<int> ptr2(ptr1); //ptr1的擁有權被轉移到ptr2
-
- auto_ptr<int> ptr3(NULL);
- ptr3 = ptr2; //ptr2的擁有權被轉移到ptr3
-
- cout<<ptr1.get()<<endl; //結果為0
- cout<<ptr2.get()<<endl; //結果為0
- cout<<*ptr3<<endl; //結果為1
auto_ptr的拷貝構造函數與賦值操作符
由于需要實現擁有權的轉移,auto_ptr的拷貝構造函數和賦值操作符,與一般類的做法不太相同。我們可以看看MinGW 5.1.6實現的auto_ptr源代碼:
- /**
- * @brief An %auto_ptr can be constructed from another %auto_ptr.
- * @param a Another %auto_ptr of the same type.
- *
- * This object now @e owns the object previously owned by @a a,
- * which has given up ownsership.
- */
- auto_ptr(auto_ptr& __a) throw() : _M_ptr(__a.release()) {}
-
- /**
- * @brief %auto_ptr assignment operator.
- * @param a Another %auto_ptr of the same type.
- *
- * This object now @e owns the object previously owned by @a a,
- * which has given up ownsership. The object that this one @e
- * used to own and track has been deleted.
- */
- auto_ptr&
- operator=(auto_ptr& __a) throw () {
- reset(__a.release());
- return *this;
- }
可以看到,auto_ptr的拷貝構造函數、賦值操作符,它們的參數都是auto_ptr& ,而不是auto_ptr const & 。
一般來說,類的拷貝構造函數和賦值操作符的參數都是const &。但是auto_ptr的做法也是合理的:確保擁有權能夠轉移 。
如果auto_ptr的拷貝構造函數和賦值操作符的參數是auto_ptr const & ,那么實參的擁有權將不能轉移。因為轉移擁有權需要修改auto_ptr的成員變量,而實參確是一個const對象,不允許修改。
右值與const &
假設我們想寫出下面的代碼:
- #include <iostream>
- #include <memory>
- using namespace std;
-
- int main(int argc, char **argv) {
- auto_ptr<int> ptr1(auto_ptr<int>(new int(1))); //使用臨時對象進行拷貝構造
- auto_ptr<int> ptr2(NULL);
- ptr2 = (auto_ptr<int>(new int(2))); //使用臨時對象進行賦值
- }
假設沒有定義auto_ptr_ref類及相關的函數,那么這段代碼將不能通過編譯。主要的原因是,拷貝構造函數及賦值操作符的參數:auto_ptr<int>(new int(1))和 auto_ptr<int>(new int(2)) 都是臨時對象 。臨時對象屬于典型的右值 ,而非const &是不能指向右值的 (參見More Effective C++ ,Item 19)。auto_ptr的拷貝構造函數及賦值操作符的參數類型恰恰是auto_ptr&,明顯 非const &。
同理,下面的兩段代碼,也不會通過編譯:
- #include <iostream>
- #include <memory>
- using namespace std;
- auto_ptr<int> f();
- int main(int argc, char **argv) {
- auto_ptr<int> ptr3(f()); //使用臨時對象進行拷貝構造
- auto_ptr<int> ptr4(NULL);
- ptr4 = f(); //使用臨時對象進行賦值
- }
- #include <iostream>
- #include <memory>
- using namespace std;
- auto_ptr<int> f(){
- return auto_ptr<int>(new int(3)); //這里其實也使用臨時對象進行拷貝構造
- }
普通類不會遇到這個問題,是因為他們的拷貝構造函數及賦值操作符(不管是用戶定義還是編譯器生成的版本),參數都是const &。
auto_ptr_ref之目的
傳說當年C++標準委員會的好多國家,因為這個問題都想把auto_ptr從標準庫中剔除。好在Bill Gibbons和Greg Colvin創造性地提出了auto_ptr_ref,解決了這一問題,世界清靜了。
auto_ptr_ref之原理
很顯然,下面的構造函數,是可以接收auto_ptr臨時對象的。
- auto_ptr(auto_ptr __a) throw() : _M_ptr(__a.release()) { }
但另一個問題也很顯然:上述構造函數不能通過編譯。如果能通過編譯,就會陷入循環調用。我們稍作修改:
- auto_ptr(auto_ptr_ref<element_type> __ref) throw() //element_type就是auto_ptr的模板參數。
- : _M_ptr(__ref._M_ptr) { }
該版本的構造函數,可以接收auto_ptr_ref的臨時對象。如果auto_ptr可以隱式轉換到auto_ptr_ref,那么我們就能夠用auto_ptr臨時對象來調用該構造函數。這個隱式轉換不難實現:
- template<typename _Tp1>
- operator auto_ptr_ref<_Tp1>() throw()
- { return auto_ptr_ref<_Tp1>(this->release()); }
至此,我們可以寫出下面的代碼,并可以通過編譯:
- #include <iostream>
- #include <memory>
- using namespace std;
-
- int main(int argc, char **argv) {
- auto_ptr<int> ptr1(auto_ptr<int>(new int(1))); //調用auto_ptr_ref版本的構造函數
- }
同理,如果我們再提供下面的函數:
- auto_ptr&
- operator=(auto_ptr_ref<element_type> __ref) throw()
- {
- if (__ref._M_ptr != this->get())
- {
- delete _M_ptr;
- _M_ptr = __ref._M_ptr;
- }
- return *this;
- }
那么,下面的代碼也可以通過編譯:
- #include <iostream>
- #include <memory>
- using namespace std;
-
- int main(int argc, char **argv) {
- auto_ptr<int> ptr2(NULL);
- ptr2 = (auto_ptr<int>(new int(2))); //調用auto_ptr_ref版本的賦值操作符
- }
auto_ptr_ref之本質
本質上,auto_ptr_ref賦予了auto_ptr“引用”的語義,這一點可以從auto_ptr_ref的注釋看出:
- /**
- * A wrapper class to provide auto_ptr with reference semantics.
- * For example, an auto_ptr can be assigned (or constructed from)
- * the result of a function which returns an auto_ptr by value.
- *
- * All the auto_ptr_ref stuff should happen behind the scenes.
- */
- template<typename _Tp1>
- struct auto_ptr_ref
- {
- _Tp1* _M_ptr;
-
- explicit
- auto_ptr_ref(_Tp1* __p): _M_ptr(__p) { }
- };
auto_ptr_ref之代碼
這里列出auto_ptr_ref相關的函數,共參考:
- auto_ptr(auto_ptr_ref<element_type> __ref) throw()
- : _M_ptr(__ref._M_ptr) {}
-
- auto_ptr&
- operator=(auto_ptr_ref<element_type> __ref) throw () {
- if (__ref._M_ptr != this->get()) {
- delete _M_ptr;
- _M_ptr = __ref._M_ptr;
- }
- return *this;
- }
-
- template<typename _Tp1>
- operator auto_ptr_ref<_Tp1>() throw () {
- return auto_ptr_ref<_Tp1> (this->release());
- }
-
- template<typename _Tp1>
- operator auto_ptr<_Tp1>() throw () {
- return auto_ptr<_Tp1> (this->release());
- }
參考資料
auto_ptr之變遷
auto_ptr實現之我見
auto_ptr_ref的奇妙(上)
auto_ptr_ref的奇妙(下)
auto_ptr_ref 的目的是什么
關于auto_ptr_ref的一點問題
左值和右值
轉自:http://www.iteye.com/topic/746062