這幾天開始拜讀侯捷先生和孟巖先生的譯作《C++標(biāo)準(zhǔn)程序庫:自修教程與參考手冊》 。兩位先生確實(shí)譯功上乘,讀得很順。但是讀到P55頁關(guān)于auto_ptr_ref的討論,卻百思不得其解:為什么需要引入auto_ptr_ref這個(gè)輔助類呢?
從書中描述來看,仿佛與拷貝構(gòu)造函數(shù) 、右值 、類型轉(zhuǎn)換 有關(guān)。于是,結(jié)合auto_ptr的源代碼,google之、baidu之,找了一推資料,終于初步 搞清該問題。
auto_ptr的擁有權(quán)
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的特性有關(guān)。auto_ptr強(qiáng)調(diào)對資源的擁有權(quán) (ownership)。也就是說,auto_ptr是"它所指對象"的擁有者。而一個(gè)對象只能屬于一個(gè)擁有者,嚴(yán)禁一物二主,否則就是重婚罪,意料外的災(zāi)難將隨之而來。
為了保證auto_ptr的擁有權(quán)唯一,auto_ptr的拷貝構(gòu)造函數(shù)和賦值操作符做了這樣一件事情:移除另一個(gè)auto_ptr的擁有權(quán) 。為了說明擁有權(quán)的轉(zhuǎn)移 ,請看下面的代碼示例:
Cpp代碼
#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ù)和賦值操作符,與一般類的做法不太相同。我們可以看看MinGW5.1.6實(shí)現(xiàn)的auto_ptr源代碼:
Cpp代碼
/**
* @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 & 。
一般來說,類的拷貝構(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對象,不允許修改。
右值與const &
假設(shè)我們想寫出下面的代碼:
Cpp代碼
#include <iostream>
#include <memory>
using namespace std;
int main(int argc, char **argv) {
auto_ptr<int> ptr1(auto_ptr<int>(new int(1))); //使用臨時(shí)對象進(jìn)行拷貝構(gòu)造
auto_ptr<int> ptr2(NULL);
ptr2 = (auto_ptr<int>(new int(2))); //使用臨時(shí)對象進(jìn)行賦值
}
假設(shè)沒有定義auto_ptr_ref類及相關(guān)的函數(shù),那么這段代碼將不能通過編譯。主要的原因是,拷貝構(gòu)造函數(shù)及賦值操作符的參數(shù):auto_ptr<int>(new int(1))和 auto_ptr<int>(new int(2)) 都是臨時(shí)對象 。臨時(shí)對象屬于典型的右值 ,而非const &是不能指向右值的(就是說給引用賦值) (參見More Effective C++ ,Item 19)。auto_ptr的拷貝構(gòu)造函數(shù)及賦值操作符的參數(shù)類型恰恰是auto_ptr&,明顯 非const &。
左值和右值
左值可以出現(xiàn)在賦值語句的左邊或右邊
右值只能出現(xiàn)在賦值語句的右邊。
例如 x*y是個(gè)右值,編譯表達(dá)式x*y=10;則出現(xiàn)錯(cuò)誤
非const引用不能綁定右值
2011-9-3 16:44
提問者: 古刃霜?jiǎng)?/a>| 瀏覽次數(shù):78次
為什么 const &a=40是對的,而&a=40是錯(cuò)的,其中具體的原理是怎樣的?
&a是對a解引用了,是a的地址,當(dāng)然不能把40復(fù)給他
而const必須在聲明時(shí)初始化,也就是const &a=40是初始化而不是把40給賦值,所以編譯能通過
同理,下面的兩段代碼,也不會(huì)通過編譯:
Cpp代碼
#include <iostream>
#include <memory>
using namespace std;
auto_ptr<int> f();
int main(int argc, char **argv) {
auto_ptr<int> ptr3(f()); //使用臨時(shí)對象進(jìn)行拷貝構(gòu)造
auto_ptr<int> ptr4(NULL);
ptr4 = f(); //使用臨時(shí)對象進(jìn)行賦值
}
Cpp代碼
#include <iostream>
#include <memory>
using namespace std;
auto_ptr<int> f(){
return auto_ptr<int>(new int(3)); //這里其實(shí)也使用臨時(shí)對象進(jìn)行拷貝構(gòu)造
}
普通類不會(huì)遇到這個(gè)問題,是因?yàn)樗麄兊目截悩?gòu)造函數(shù)及賦值操作符(不管是用戶定義還是編譯器生成的版本),參數(shù)都是const &。
auto_ptr_ref之目的
傳說當(dāng)年C++標(biāo)準(zhǔn)委員會(huì)的好多國家,因?yàn)檫@個(gè)問題都想把a(bǔ)uto_ptr從標(biāo)準(zhǔn)庫中剔除。好在Bill Gibbons和Greg Colvin創(chuàng)造性地提出了auto_ptr_ref,解決了這一問題,世界清靜了。
auto_ptr_ref之原理
很顯然,下面的構(gòu)造函數(shù),是可以接收auto_ptr臨時(shí)對象的。
Cpp代碼
auto_ptr(auto_ptr __a) throw() : _M_ptr(__a.release()) { }
但另一個(gè)問題也很顯然:上述構(gòu)造函數(shù)不能通過編譯。如果能通過編譯,就會(huì)陷入循環(huán)調(diào)用。我們稍作修改:
Cpp代碼
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í)對象。如果auto_ptr可以隱式轉(zhuǎn)換到auto_ptr_ref,那么我們就能夠用auto_ptr臨時(shí)對象來調(diào)用該構(gòu)造函數(shù)。這個(gè)隱式轉(zhuǎn)換不難實(shí)現(xiàn):
Cpp代碼
template<typename _Tp1>
operator auto_ptr_ref<_Tp1>() throw()
{ return auto_ptr_ref<_Tp1>(this->release()); }
至此,我們可以寫出下面的代碼,并可以通過編譯:
Cpp代碼
#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ù)
}
同理,如果我們再提供下面的函數(shù):
Cpp代碼
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;
}
那么,下面的代碼也可以通過編譯:
Cpp代碼
#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“引用”的語義,這一點(diǎn)可以從auto_ptr_ref的注釋看出:
Cpp代碼
/**
* 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ù),共參考:
Cpp代碼