這幾天開(kāi)始拜讀侯捷先生和孟巖先生的譯作《C++標(biāo)準(zhǔn)程序庫(kù):自修教程與參考手冊(cè)》 。兩位先生確實(shí)譯功上乘,讀得很順。但是讀到P55頁(yè)關(guān)于auto_ptr_ref的討論,卻百思不得其解:為什么需要引入auto_ptr_ref這個(gè)輔助類(lèi)呢?
從書(shū)中描述來(lái)看,仿佛與拷貝構(gòu)造函數(shù) 、右值 、類(lèi)型轉(zhuǎn)換 有關(guān)。于是,結(jié)合auto_ptr的源代碼,google之、baidu之,找了一推資料,終于初步 搞清該問(wèn)題。
auto_ptr的擁有權(quán)
C++常見(jiàn)的智能指針有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卻沒(méi)有shared_ptr_ref呢?
答案與auto_ptr的特性有關(guān)。auto_ptr強(qiáng)調(diào)對(duì)資源的擁有權(quán) (ownership)。也就是說(shuō),auto_ptr是"它所指對(duì)象"的擁有者。而一個(gè)對(duì)象只能屬于一個(gè)擁有者,嚴(yán)禁一物二主,否則就是重婚罪,意料外的災(zāi)難將隨之而來(lái)。
為了保證auto_ptr的擁有權(quán)唯一,auto_ptr的拷貝構(gòu)造函數(shù)和賦值操作符做了這樣一件事情:移除另一個(gè)auto_ptr的擁有權(quán) 。為了說(shuō)明擁有權(quán)的轉(zhuǎn)移 ,請(qǐng)看下面的代碼示例:
- #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的擁有權(quán)被轉(zhuǎn)移到ptr2
-
- auto_ptr<int> ptr3(NULL);
- ptr3 = ptr2; //ptr2的擁有權(quán)被轉(zhuǎn)移到ptr3
-
- cout<<ptr1.get()<<endl; //結(jié)果為0
- cout<<ptr2.get()<<endl; //結(jié)果為0
- cout<<*ptr3<<endl; //結(jié)果為1
auto_ptr的拷貝構(gòu)造函數(shù)與賦值操作符
由于需要實(shí)現(xiàn)擁有權(quán)的轉(zhuǎn)移,auto_ptr的拷貝構(gòu)造函數(shù)和賦值操作符,與一般類(lèi)的做法不太相同。我們可以看看MinGW 5.1.6實(shí)現(xiàn)的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的拷貝構(gòu)造函數(shù)、賦值操作符,它們的參數(shù)都是auto_ptr& ,而不是auto_ptr const & 。
一般來(lái)說(shuō),類(lèi)的拷貝構(gòu)造函數(shù)和賦值操作符的參數(shù)都是const &。但是auto_ptr的做法也是合理的:確保擁有權(quán)能夠轉(zhuǎn)移 。
如果auto_ptr的拷貝構(gòu)造函數(shù)和賦值操作符的參數(shù)是auto_ptr const & ,那么實(shí)參的擁有權(quán)將不能轉(zhuǎn)移。因?yàn)檗D(zhuǎn)移擁有權(quán)需要修改auto_ptr的成員變量,而實(shí)參確是一個(gè)const對(duì)象,不允許修改。
右值與const &
假設(shè)我們想寫(xiě)出下面的代碼:
- #include <iostream>
- #include <memory>
- using namespace std;
-
- int main(int argc, char **argv) {
- auto_ptr<int> ptr1(auto_ptr<int>(new int(1))); //使用臨時(shí)對(duì)象進(jìn)行拷貝構(gòu)造
- auto_ptr<int> ptr2(NULL);
- ptr2 = (auto_ptr<int>(new int(2))); //使用臨時(shí)對(duì)象進(jìn)行賦值
- }
假設(shè)沒(méi)有定義auto_ptr_ref類(lèi)及相關(guān)的函數(shù),那么這段代碼將不能通過(guò)編譯。主要的原因是,拷貝構(gòu)造函數(shù)及賦值操作符的參數(shù):auto_ptr<int>(new int(1))和 auto_ptr<int>(new int(2)) 都是臨時(shí)對(duì)象 。臨時(shí)對(duì)象屬于典型的右值 ,而非const &是不能指向右值的 (參見(jiàn)More Effective C++ ,Item 19)。auto_ptr的拷貝構(gòu)造函數(shù)及賦值操作符的參數(shù)類(lèi)型恰恰是auto_ptr&,明顯 非const &。
同理,下面的兩段代碼,也不會(huì)通過(guò)編譯:
- #include <iostream>
- #include <memory>
- using namespace std;
- auto_ptr<int> f();
- int main(int argc, char **argv) {
- auto_ptr<int> ptr3(f()); //使用臨時(shí)對(duì)象進(jìn)行拷貝構(gòu)造
- auto_ptr<int> ptr4(NULL);
- ptr4 = f(); //使用臨時(shí)對(duì)象進(jìn)行賦值
- }
- #include <iostream>
- #include <memory>
- using namespace std;
- auto_ptr<int> f(){
- return auto_ptr<int>(new int(3)); //這里其實(shí)也使用臨時(shí)對(duì)象進(jìn)行拷貝構(gòu)造
- }
普通類(lèi)不會(huì)遇到這個(gè)問(wèn)題,是因?yàn)樗麄兊目截悩?gòu)造函數(shù)及賦值操作符(不管是用戶(hù)定義還是編譯器生成的版本),參數(shù)都是const &。
auto_ptr_ref之目的
傳說(shuō)當(dāng)年C++標(biāo)準(zhǔn)委員會(huì)的好多國(guó)家,因?yàn)檫@個(gè)問(wèn)題都想把a(bǔ)uto_ptr從標(biāo)準(zhǔn)庫(kù)中剔除。好在Bill Gibbons和Greg Colvin創(chuàng)造性地提出了auto_ptr_ref,解決了這一問(wèn)題,世界清靜了。
auto_ptr_ref之原理
很顯然,下面的構(gòu)造函數(shù),是可以接收auto_ptr臨時(shí)對(duì)象的。
- auto_ptr(auto_ptr __a) throw() : _M_ptr(__a.release()) { }
但另一個(gè)問(wèn)題也很顯然:上述構(gòu)造函數(shù)不能通過(guò)編譯。如果能通過(guò)編譯,就會(huì)陷入循環(huán)調(diào)用。我們稍作修改:
- auto_ptr(auto_ptr_ref<element_type> __ref) throw() //element_type就是auto_ptr的模板參數(shù)。
- : _M_ptr(__ref._M_ptr) { }
該版本的構(gòu)造函數(shù),可以接收auto_ptr_ref的臨時(shí)對(duì)象。如果auto_ptr可以隱式轉(zhuǎn)換到auto_ptr_ref,那么我們就能夠用auto_ptr臨時(shí)對(duì)象來(lái)調(diào)用該構(gòu)造函數(shù)。這個(gè)隱式轉(zhuǎn)換不難實(shí)現(xiàn):
- template<typename _Tp1>
- operator auto_ptr_ref<_Tp1>() throw()
- { return auto_ptr_ref<_Tp1>(this->release()); }
至此,我們可以寫(xiě)出下面的代碼,并可以通過(guò)編譯:
- #include <iostream>
- #include <memory>
- using namespace std;
-
- int main(int argc, char **argv) {
- auto_ptr<int> ptr1(auto_ptr<int>(new int(1))); //調(diào)用auto_ptr_ref版本的構(gòu)造函數(shù)
- }
同理,如果我們?cè)偬峁┫旅娴暮瘮?shù):
- 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;
- }
那么,下面的代碼也可以通過(guò)編譯:
- #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))); //調(diào)用auto_ptr_ref版本的賦值操作符
- }
auto_ptr_ref之本質(zhì)
本質(zhì)上,auto_ptr_ref賦予了auto_ptr“引用”的語(yǔ)義,這一點(diǎn)可以從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相關(guān)的函數(shù),共參考:
- 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實(shí)現(xiàn)之我見(jiàn)
auto_ptr_ref的奇妙(上)
auto_ptr_ref的奇妙(下)
auto_ptr_ref 的目的是什么
關(guān)于auto_ptr_ref的一點(diǎn)問(wèn)題
左值和右值
轉(zhuǎn)自:http://www.iteye.com/topic/746062