auto_ptr源碼 const auto_ptr保證擁有權(quán)不能轉(zhuǎn)移的實(shí)現(xiàn)原理?
-----------------------------------------------------------------------------------------------
在《C++標(biāo)準(zhǔn)程序庫》p55,提到了auto_ptr使用了一個(gè)技巧,能夠copy和復(fù)制non-const auto_ptr,但不可以copy和復(fù)制const atuo_ptr。
//auto_ptr的源碼:
template
struct auto_ptr_ref
{
_Tp1* _M_ptr;
explicit
auto_ptr_ref(_Tp1* __p): _M_ptr(__p) { }
};
template
class auto_ptr
{
private:
_Tp* _M_ptr;
public:
typedef _Tp element_type;
explicit
auto_ptr(element_type* __p = 0) throw() : _M_ptr(__p) { }
auto_ptr(auto_ptr& __a) throw() : _M_ptr(__a.release()) { }
template
auto_ptr(auto_ptr<_Tp1>& __a) throw() : _M_ptr(__a.release()) { }
auto_ptr&
operator=(auto_ptr& __a) throw()
{
reset(__a.release());
return *this;
}
template
auto_ptr&
operator=(auto_ptr<_Tp1>& __a) throw()
{
reset(__a.release());
return *this;
}
~auto_ptr() { delete _M_ptr; }
element_type&
operator*() const throw()
{
_GLIBCXX_DEBUG_ASSERT(_M_ptr != 0);
return *_M_ptr;
}
element_type*
operator->() const throw()
{
_GLIBCXX_DEBUG_ASSERT(_M_ptr != 0);
return _M_ptr;
}
element_type*
get() const throw() { return _M_ptr; }
element_type*
release() throw()
{
element_type* __tmp = _M_ptr;
_M_ptr = 0;
return __tmp;
}
void
reset(element_type* __p = 0) throw()
{
if (__p != _M_ptr)
{
delete _M_ptr;
_M_ptr = __p;
}
}
auto_ptr(auto_ptr_ref __ref) throw()
: _M_ptr(__ref._M_ptr) { }
auto_ptr&
operator=(auto_ptr_ref __ref) throw()
{
if (__ref._M_ptr != this->get())
{
delete _M_ptr;
_M_ptr = __ref._M_ptr;
}
return *this;
}
template
operator auto_ptr_ref<_Tp1>() throw()
{ return auto_ptr_ref<_Tp1>(this->release()); }
template
operator auto_ptr<_Tp1>() throw()
{ return auto_ptr<_Tp1>(this->release()); }
};//VC7中的
---------------------------------------------------------------------------------------------------------------
// TEMPLATE CLASS auto_ptr
template
class auto_ptr;
template
struct auto_ptr_ref
{ // proxy reference for auto_ptr copying
auto_ptr_ref(auto_ptr<_Ty>& _Right)
: _Ref(_Right)
{ // construct from compatible auto_ptr
}
auto_ptr<_Ty>& _Ref; // reference to constructor argument
};
template
class auto_ptr
{ // wrap an object pointer to ensure destruction
public:
typedef _Ty element_type;
explicit auto_ptr(_Ty *_Ptr = 0) _THROW0()
: _Myptr(_Ptr)
{ // construct from object pointer
}
auto_ptr(auto_ptr<_Ty>& _Right) _THROW0()
: _Myptr(_Right.release())
{ // construct by assuming pointer from _Right auto_ptr
}
auto_ptr(auto_ptr_ref<_Ty> _Right) _THROW0()
: _Myptr(_Right._Ref.release())
{ // construct by assuming pointer from _Right auto_ptr_ref
}
template
operator auto_ptr<_Other>() _THROW0()
{ // convert to compatible auto_ptr
return (auto_ptr<_Other>(*this));
}
template
operator auto_ptr_ref<_Other>() _THROW0()
{ // convert to compatible auto_ptr_ref
return (auto_ptr_ref<_Other>(*this));
}
template
auto_ptr<_Ty>& operator=(auto_ptr<_Other>& _Right) _THROW0()
{ // assign compatible _Right (assume pointer)
reset(_Right.release());
return (*this);
}
template
auto_ptr(auto_ptr<_Other>& _Right) _THROW0()
: _Myptr(_Right.release())
{ // construct by assuming pointer from _Right
}
auto_ptr<_Ty>& operator=(auto_ptr<_Ty>& _Right) _THROW0()
{ // assign compatible _Right (assume pointer)
reset(_Right.release());
return (*this);
}
auto_ptr<_Ty>& operator=(auto_ptr_ref<_Ty>& _Right) _THROW0()
{ // assign compatible _Right._Ref (assume pointer)
reset(_Right._Ref.release());
return (*this);
}
~auto_ptr()
{ // destroy the object
delete _Myptr;
}
_Ty& operator*() const _THROW0()
{ // return designated value
return (*_Myptr);
}
_Ty *operator->() const _THROW0()
{ // return pointer to class object
return (&**this);
}
_Ty *get() const _THROW0()
{ // return wrapped pointer
return (_Myptr);
}
_Ty *release() _THROW0()
{ // return wrapped pointer and give up ownership
_Ty *_Tmp = _Myptr;
_Myptr = 0;
return (_Tmp);
}
void reset(_Ty* _Ptr = 0)
{ // destroy designated object and store new pointer
if (_Ptr != _Myptr)
delete _Myptr;
_Myptr = _Ptr;
}
private:
_Ty *_Myptr; // the wrapped object pointer
};
----------------------------------------------------------------------------------------------------------
C++中的auto_ptr (修改版本) stl 文件中的 std::auto_ptr 在C++中的故事特別多, 在它的演變過程中至少出現(xiàn)了3個(gè)版本.
http://www.josuttis.com/libbook/auto_ptr.html 這個(gè)連接里面有它完整的故事.
VC6中STL帶的auto_ptr( 帶owner字段)的版本應(yīng)該就是文中說的Version 2.
最新的Version里面包含了一個(gè)auto_ptr_ref, 這個(gè)是當(dāng)將auto_ptr作為函數(shù)返回值和函數(shù)參數(shù)時(shí)需要引入的
一個(gè)"額外間接層".
下面是它的一些說明:
auto_ptr的拷貝構(gòu)造函數(shù)和一般我們常見的不同, 它的參數(shù)rhs并不是const reference, 而是refence,
auto_ptr( /*const */ auto_ptr& rhs)
{
...
}
假設(shè)我們需要將一個(gè)auto_ptr作為某個(gè)函數(shù)的返回值, 例如
auto_ptr source()
{
return auto_ptr(new int(3));
}
那么我們?nèi)绾卧赾aller中得到返回的結(jié)果呢?
理所當(dāng)然的語法是:
auto_ptr p( source() ); (拷貝構(gòu)造函數(shù))
或者
auto_ptr p = source(); (拷貝構(gòu)造函數(shù))
或者
auto_ptr p = ...; (operator=)
p = source();
但是如果沒有auto_ptr_ref的存在, 上面這些行實(shí)際上應(yīng)該是一個(gè)編譯錯(cuò)誤(VC6不報(bào)錯(cuò)), 原因是:
C++中有左值/右值之分, 函數(shù)如果返回值, 那么是r-value. 右值作為reference函數(shù)參數(shù)時(shí), 只能是const reference.
因此source函數(shù)返回的auto_ptr作為rhs實(shí)參調(diào)用auto_ptr的拷貝構(gòu)造函數(shù)時(shí), 只能是const refernce, 但是
這個(gè)函數(shù)的簽名需要rhs為reference, 因此無法編譯.
舉個(gè)最簡單的例子:
有函數(shù):
int foo() { return 0; }
void bar(int & i) { }
調(diào)用
int& i = foo() ; //錯(cuò)誤
const int& i = foo(); //OK
bar(foo()) //錯(cuò)誤
同理, 拷貝構(gòu)造函數(shù)不過是一個(gè)特殊的"函數(shù)"而已, 我們上面的source函數(shù)返回的auto_ptr對(duì)象也只能作為一個(gè)
const auto_ptr&, 但是這個(gè)拷貝構(gòu)造函數(shù)需要的參數(shù)原型是auto_ptr&, 而不是const auto_ptr& .
因此auto_ptr引入了一個(gè)'額外的間接層' auto_ptr_ref, 來完成一個(gè)從r-value到l-value之間的過渡.
基本的思路是;
提供另外一個(gè)構(gòu)造函數(shù), 接受一個(gè)以值傳遞的auto_ptr_ref:
auto_ptr( auto_ptr_ref ref)
{
....
}
然后在auto_ptr類中, 提供一個(gè)自動(dòng)轉(zhuǎn)型的函數(shù)
operator auto_ptr_ref ()
{
.....
}
這樣, source返回一個(gè)auto_ptr, 編譯器嘗試調(diào)用拷貝構(gòu)造函數(shù), 發(fā)現(xiàn)參數(shù)不必配(期望const), 然后發(fā)現(xiàn)了一個(gè)自動(dòng)轉(zhuǎn)型的
operator auto_ptr_ref()函數(shù), 而后又發(fā)現(xiàn)通過調(diào)用該自動(dòng)轉(zhuǎn)型得到一個(gè)auto_ptr_ref對(duì)象后, 可以調(diào)用caller的
auto_ptr的以auto_ptr_ref為參數(shù)的非explicit的構(gòu)造函數(shù), 完成了一個(gè)auto_ptr到另外一個(gè)auto_ptr之間的復(fù)制過程.
注意一點(diǎn): operator auto_ptr_ref () 不是const成員函數(shù).
C++語法規(guī)則中對(duì)于臨時(shí)變量的r-value有個(gè)詭秘的地方就是:
如果你需要將r-value保存在一個(gè)reference中, 或者作為某個(gè)refence的函數(shù)參數(shù), 那么必須為const reference,
但是你也可以在一個(gè)完整的表達(dá)式中直接使用這個(gè)臨時(shí)變量, 這種情況下該臨時(shí)變量實(shí)際上并不是作為const reference對(duì)待,
因?yàn)槟憧梢哉{(diào)用它的非const成員函數(shù). 例如:
class Integer
{
public:
void zero() { i = 0; }
private:
int i;
};
class Integer
{
public:
void zero() { i = 0; }
private:
int i;
};
Integer().zero(); //創(chuàng)建一個(gè)Integer的臨時(shí)變量, 然后調(diào)用非const成員函數(shù).
這樣, auto_ptr p( source() );
實(shí)際上發(fā)生的事情就是:
auto_ptr p( source().operator auto_ptr_ref());
通過source()函數(shù)得到一個(gè)臨時(shí)的auto_ptr對(duì)象, 然后調(diào)用其中的自動(dòng)轉(zhuǎn)換函數(shù)得到一個(gè)auto_ptr_ref,
即使該轉(zhuǎn)型函數(shù)是非const成員函數(shù)仍然可行.
然后調(diào)用 p 對(duì)象的以auto_ptr_ref為參數(shù)的構(gòu)造函數(shù)進(jìn)行復(fù)制.
然后, source()函數(shù)創(chuàng)建的臨時(shí)對(duì)象在整個(gè)表達(dá)式結(jié)束后被析構(gòu), 當(dāng)然這個(gè)時(shí)候這個(gè)臨時(shí)對(duì)象內(nèi)部的指針已經(jīng)被reset(0)了,
因?yàn)樵撝羔樀膿碛袡?quán)已經(jīng)被p接管了. (否則會(huì)重復(fù)刪除)
std::auto_ptr在很多情況下是很便利的, 例如一個(gè)函數(shù)內(nèi)部需要通過new分配一個(gè)結(jié)構(gòu), 那么誰來釋放是一個(gè)問題,
一種原則是誰分配, 誰釋放, 但是對(duì)于這種情況顯然不合適.
利用auto_ptr就簡單得多了: 誰擁有誰釋放, 誰都不要那么就編譯器自動(dòng)釋放它. 例如
有個(gè)函數(shù):
auto_ptr create_sth()
{
auto_ptr p(new sth);
return p;
}
調(diào)用1:
auto_ptr p = create_sth();
...
p退出作用域, 自動(dòng)釋放.
調(diào)用2:
create_sth();
沒有人接受這個(gè)返回的對(duì)象, 那么編譯器自動(dòng)會(huì)調(diào)用auto_ptr的析構(gòu)函數(shù)釋放之.
sink也是一個(gè)作用:
例如我已經(jīng)擁有一個(gè)auto_ptr對(duì)象的指針, 那么我可以定義一個(gè)sink函數(shù), 原型如下:
void sink(auto_ptr p)
{
}
正如sink名字暗示的一樣, sink函數(shù)起到一個(gè)吸收作用, 將某個(gè)外部的auto_ptr對(duì)象吸收過來,類似于宇宙中的"黑洞".
例如:
auto_ptr p(new sth);
sink(p);
//這里, p指向null了, p所指的sth對(duì)象已經(jīng)被sink函數(shù)"吸收"了.
當(dāng)然為了防止這種情況在你不注意的情況下發(fā)生, 你可以
const auto_ptr p(new sth);
sink(p); //編譯錯(cuò)誤
auto_ptr p2 = p; //編譯錯(cuò)誤
這樣, 一個(gè)const auto_ptr的對(duì)象一旦構(gòu)造完成, 永遠(yuǎn)不會(huì)失去對(duì)該對(duì)象的擁有權(quán). "一旦擁有, 從不失去".
當(dāng)然auto_ptr最重要的作用, 也是它的原始目的是為了提供異常安全.
auto_ptr
動(dòng)態(tài)內(nèi)存使用最多的是在C++應(yīng)用程序的代碼中。有過編程經(jīng)驗(yàn)的程序員雖然都知道new操作符的使用一定要與delete匹配,在某些場(chǎng)合仍然可能有內(nèi)存溢出。當(dāng)異常被擲出時(shí),程序的正??刂屏鞒瘫桓淖?,因此導(dǎo)致潛在的內(nèi)存溢出。例如,
void g() //可能擲出
{
if (some_condition == false)
throw X();
}
void func()
{
string * pstr = new string;
g(); //如果 g 擲出一個(gè)異常,內(nèi)存溢出
delete pstr; //如果 g 擲出一個(gè)異常,則此行為不能達(dá)到的代碼行。
}
int main()
{
try
{
func();
}
catch(...)
{}
}
當(dāng) g 擲出一個(gè)異常,異常處理機(jī)制展開堆棧:g()退出,同時(shí)控制被轉(zhuǎn)移到 main() 的 catch(...)代碼塊。這時(shí),無論怎樣,func()中的delete語句都不會(huì)被執(zhí)行,由此導(dǎo)致pstr的內(nèi)存溢出。要是使用局部自動(dòng)串變量,而不是使用動(dòng)態(tài)分配-內(nèi)存溢出就不會(huì)出現(xiàn)了:
string str; //局部自動(dòng)對(duì)象
g(); //沒有內(nèi)存溢出
許多數(shù)據(jù)重要的結(jié)構(gòu)以及應(yīng)用,象鏈表,STL容器,串,數(shù)據(jù)庫系統(tǒng)以及交互式應(yīng)用必須使用動(dòng)態(tài)內(nèi)存分配,因此仍然冒著萬一發(fā)生異常導(dǎo)致內(nèi)存溢出的風(fēng)險(xiǎn)。C++標(biāo)準(zhǔn)化委員會(huì)意識(shí)到了這個(gè)漏洞并在標(biāo)準(zhǔn)庫中添加了一個(gè)特殊的類模板,它就是std::auto_ptr,其目的是促使動(dòng)態(tài)內(nèi)存和異常之前進(jìn)行平滑的交互。Auto_ptr保證當(dāng)異常擲出時(shí)分配的對(duì)象(即:new操作符分配的對(duì)象)能被自動(dòng)銷毀,內(nèi)存能被自動(dòng)釋放。下面我們就來討論使用動(dòng)態(tài)內(nèi)存時(shí),如何正確和有效地使用auto_ptr來避免資源溢出。這個(gè)技術(shù)適用于文件,線程,鎖定以及與此類似的資源。
Auto_ptr的定義可以在中找到。與標(biāo)準(zhǔn)庫中其它的成員一樣,它被聲明在命名空間std::中。當(dāng)你實(shí)例化auto_ptr對(duì)象時(shí),對(duì)它進(jìn)行初始化的方法是用一個(gè)指針指向動(dòng)態(tài)分配的對(duì)象,下面是實(shí)例化和初始化auto_ptr對(duì)象的例子:
include
#include
using namespace std;
void func()
{
auto_ptr pstr (new string); /* 創(chuàng)建并初始化auto_ptr */
}
auto_ptr后面的尖括弧里指定auto_ptr指針的類型,在這個(gè)例子中是string。然后auto_ptr句柄的名字,在這個(gè)例子中是pstr。最后是用動(dòng)態(tài)分配的對(duì)象指針初始化這個(gè)實(shí)例。注意你只能使用auto_ptr構(gòu)造器的拷貝,也就是說,下面的代碼是非法的:
auto_ptr pstr = new string; //編譯出錯(cuò)
Auto_ptr是一個(gè)模板,因此它是完全通用的。它可以指向任何類型的對(duì)象,包括基本的數(shù)據(jù)類型:
auto_ptr pi (new int);
一旦你實(shí)例化一個(gè)auto_ptr,并用動(dòng)態(tài)分配的對(duì)象地址對(duì)它進(jìn)行了初始化,就可以將它當(dāng)作普通的對(duì)象指針使用,例如:
*pstr = "hello world"; //賦值
pstr->size(); //調(diào)用成員函數(shù)
之所以能這樣做是因?yàn)閍uto_ptr重載了操作符&,*和->。不要被語法誤導(dǎo),記住pstr是一個(gè)對(duì)象,不是一個(gè)指針。
auto_ptr是如何解決前面提到的內(nèi)存溢出問題呢?auto_ptr的析構(gòu)函數(shù)自動(dòng)摧毀它綁定的動(dòng)態(tài)分配對(duì)象。換句話說,當(dāng)pstr的析構(gòu)函數(shù)執(zhí)行時(shí),它刪除構(gòu)造pstr期間創(chuàng)建的串指針。你絕不能刪除auto_ptr,因?yàn)樗且粋€(gè)本地對(duì)象,它的析構(gòu)函數(shù)是被自動(dòng)調(diào)用的。讓我們看一下函數(shù)func()的修訂版本,這次使用了auto_ptr:
void func()
{
auto_ptr pstr (new string);
g(); //如果g()擲出異常,pstr 被自動(dòng)摧毀
}
C++保證在堆棧展開過程中,自動(dòng)存儲(chǔ)類型的對(duì)象被自動(dòng)摧毀。因此,如果g()擲出異常,pstr的析構(gòu)函數(shù)將會(huì)在控制被轉(zhuǎn)移到catch(...)塊之前執(zhí)行。因?yàn)閜str的析構(gòu)函數(shù)刪除其綁定的串指針,所以不會(huì)有內(nèi)存溢出發(fā)生。這樣我們?cè)谑褂脛?dòng)態(tài)分配對(duì)象時(shí),利用auto_ptr就實(shí)現(xiàn)了自動(dòng)和安全的本地對(duì)象。
如何避免使用auto_ptr的缺陷
auto_ptr并不是完美無缺的,它的確很方便,但也有缺陷,在使用時(shí)要注意避免。首先,不要將auto_ptr對(duì)象作為STL容器的元素。C++標(biāo)準(zhǔn)明確禁止這樣做,否則可能會(huì)碰到不可預(yù)見的結(jié)果(在另文中討論)。
auto_ptr的另一個(gè)缺陷是將數(shù)組作為auto_ptr的參數(shù):
auto_ptr pstr (new char[12] ); //數(shù)組;為定義
記住不管什么時(shí)候使用數(shù)組的new操作時(shí),必須要用delete[]來摧毀數(shù)組。因?yàn)閍uto_ptr的析構(gòu)函數(shù)只對(duì)非數(shù)組類型起作用。所以數(shù)組是不能被正確摧毀的話,程序的行為是不明確的??傊?,auto_ptr控制一個(gè)由new分配的單對(duì)象指針,僅此而已。
--------------------------------------------------------------------------------------------------
Misusing auto_ptrs
auto_ptrs satisfy a certain need; namely, to avoid resource leaks when exception handling is used. Unfortunately, the exact behavior of auto_ptrs changed in the past and no other kind of smart pointers are provided in the C++ standard library, so people tend to misuse auto_ptrs. Here are some hints to help you use them correctly:
-
auto_ptrs cannot share ownership.
An auto_ptr must not refer to an object that is owned by another auto_ptr (or other object). Otherwise, if the first pointer deletes the object, the other pointer suddenly refers to a destroyed object, and any further read or write access may result in disaster.
-
auto_ptrs are not provided for arrays.
An auto_ptr is not allowed to refer to arrays. This is because an auto_ptr calls delete instead of delete [] for the object it owns. Note that there is no equivalent class in the C++ standard library that has the auto_ptr semantics for arrays. Instead, the library provides several container classes to handle collections of data (see Chapter 5).
-
auto_ptrs are not "universal smart pointers."
An auto_ptr is not designed to solve other problems for which smart pointers might be useful. In particular, they are not pointers for reference counting. (Pointers for reference counting ensure that an object gets deleted only if the last of several smart pointers that refer to that object gets destroyed.)
-
auto_ptrs don't meet the requirements for container elements.
An auto_ptr does not meet one of the most fundamental requirements for elements of standard containers. That is, after a copy or an assignment of an auto_ptr, source and sink are not equivalent. In fact, when an auto_ptr is assigned or copied, the source auto_ptr gets modified because it transfers its value rather than copying it. So you should not use an auto_ptr as an element of a standard container. Fortunately, the design of the language and library prevents this misuse from compiling in a standard-conforming environment.