基本内容
C++的auto_ptr所做的事情,就是動态分配對象以及當對象不再需要時自動執行清理。
它的源代碼:
namespace
class auto_ptr {
public: // constructor & destructor ----------------------------------- (1)
explicit auto_ptr (T* ptr =0) throw~auto_ptr() throw// Copy & assignment --------------------------------------------(2)
auto_ptr (auto_ptr& rhs) throw() :ap(rhs.release()) {}
template& rhs) throw() : ap(rhs.release()) { }
auto_ptr&operator= (auto_ptr& rhs) throw()
{
reset(rhs.release());
return*this
auto_ptr&operator= (auto_ptr& rhs) throw()
{
reset(rhs.release());
return*this// Dereference----------------------------------------------------(3)
T&operator*() constthrowreturn**operator->() constthrowreturn// Helper functions------------------------------------------------(4)
// value access
T*get() constthrowreturn// release ownership
T* release() throw*=0return// reset value
void reset (T* ptr=0) throwif (ap != ptr)
{
delete ap;
ap =// Special conversions-----------------------------------------------(5)
template
struct** rhs) : yp(rhs) {}
};
auto_ptr(auto_ptr_ref rhs) throw() : ap(rhs.yp) { }
auto_ptr&operator= (auto_ptr_ref rhs) throwreturn*this
operator auto_ptr_ref() throwreturn auto_ptr_ref
operator auto_ptr() throwreturn auto_ptr
1 構造函數與析構函數
auto_ptr在構造時獲取對某個對象的所有權(ownership),在析構時釋放該對象。我們可以這樣使用auto_ptr來提高代碼安全性:int* p = new int(0);auto_ptr ap(p);
從此我們不必關心應該何時釋放p,也不用擔心發生異常會有内存洩漏。
這裡我們有幾點要注意:
1) 因為auto_ptr析構的時候肯定會删除他所擁有的那個對象,所有我們就要注意了,一個蘿蔔一個坑,兩個auto_ptr不能同時擁有同一個對象。像這樣:
int* p = new int(0);
auto_ptr ap1(p);
auto_ptr ap2(p);
因為ap1與ap2都認為指針p是歸它管的,在析構時都試圖删除p,兩次删除同一個對象的行為在C++标準中是未定義的。所以我們必須防止這樣使用auto_ptr.
2) 考慮下面這種用法:
int* pa = new int;
auto_ptr ap(pa);
因為auto_ptr的析構函數中删除指針用的是delete,而不是delete [],所以我們不應該用auto_ptr來管理一個數組指針。
3) 構造函數的explicit關鍵詞有效阻止從一個“裸”指針隐式轉換成auto_ptr類型。
4) 因為C++保證删除一個空指針是安全的,所以我們沒有必要把析構函數寫成:
~auto_ptr() throw()
{
if(ap) delete ap;
}
2 拷貝構造與賦值
與引用計數型智能指針不同的,auto_ptr要求其對“裸”指針的完全占有性。也就是說一個”裸“指針不能同時被兩個以上的auto_ptr所擁有。那麼,在拷貝構造或賦值操作時,我們必須作特殊的處理來保證這個特性。auto_ptr的做法是“所有權轉移”,即拷貝或賦值的源對象将失去對“裸”指針的所有權,所以,與一般拷貝構造函數,賦值函數不同,auto_ptr的拷貝構造函數,賦值函數的參數為引用而不是常引用(const reference).當然,一個auto_ptr也不能同時擁有兩個以上的“裸”指針,所以,拷貝或賦值的目标對象将先釋放其原來所擁有的對象。
這裡的注意點是:
1) 因為一個auto_ptr被拷貝或被賦值後,其已經失去對原對象的所有權,這個時候,對這個auto_ptr的提領(dereference)操作是不安全的。如下:
int* p = new int(0);
auto_ptr ap1(p);
auto_ptr ap2 = ap1;
cout<<*ap1; //錯誤,此時ap1隻剩一個null指針在手了
這種情況較為隐蔽的情形出現在将auto_ptr作為函數參數按值傳遞,因為在函數調用過程中在函數的作用域中會産生一個局部對象來接收傳入的auto_ptr(拷貝構造),這樣,傳入的實參auto_ptr就失去了其對原對象的所有權,而該對象會在函數退出時被局部auto_ptr删除。如下:
void f(auto_ptr ap){cout<<*ap;}
auto_ptr ap1(new int(0));
f(ap1);
cout<<*ap1; //錯誤,經過f(ap1)函數調用,ap1已經不再擁有任何對象了。
因為這種情況太隐蔽,太容易出錯了,所以auto_ptr作為函數參數按值傳遞是一定要避免的。或許大家會想到用auto_ptr的指針或引用作為函數參數或許可以,但是仔細想想,我們并不知道在函數中對傳入的auto_ptr做了什麼,如果當中某些操作使其失去了對對象的所有權,那麼這還是可能會導緻緻命的執行期錯誤。也許,用const reference的形式來傳遞auto_ptr會是一個不錯的選擇。
2)我們可以看到拷貝構造函數與賦值函數都提供了一個成員模闆在不複蓋“正統”版本的情況下實現auto_ptr的隐式轉換。如我們有以下兩個類
class base{};
class derived: public base{};
那麼下列代碼就可以通過,實現從auto_ptr到auto_ptr的隐式轉換,因為derived*可以轉換成base*類型
auto_ptr apbase = auto_ptr(new derived);
3) 因為auto_ptr不具有值語義(value semantic), 所以auto_ptr不能被用在stl标準容器中。
所謂值語義,是指符合以下條件的類型(假設有類A):
A a1;
A a2(a1);
A a3;
a3 = a1;
那麼
a2 == a1, a3 == a1
很明顯,auto_ptr不符合上述條件,而我們知道stl标準容器要用到大量的拷貝賦值操作,并且假設其操作的類型必須符合以上條件。
3 提領操作(dereference)
提領操作有兩個操作,一個是返回其所擁有的對象的引用,另一個是則實現了通過auto_ptr調用其所擁有的對象的成員。如:
struct A
{
void f();
}
auto_ptr apa(new A);
(*apa).f();
apa->f();
當然,我們首先要确保這個智能指針确實擁有某個對象,否則,這個操作的行為即對空指針的提領是未定義的。
4 輔助函數
1) get用來顯式的返回auto_ptr所擁有的對象指針。我們可以發現,标準庫提供的auto_ptr既不提供從“裸”指針到auto_ptr的隐式轉換(構造函數為explicit),也不提供從auto_ptr到“裸”指針的隐式轉換,從使用上來講可能不那麼的靈活,考慮到其所帶來的安全性還是值得的。
2) release,用來轉移所有權
3) reset,用來接收所有權,如果接收所有權的auto_ptr如果已經擁有某對象,必須先釋放該對象。
5 特殊轉換
這裡提供一個輔助類auto_ptr_ref來做特殊的轉換,按照标準的解釋,這個類及下面4個函數的作用是:使我們得以拷貝和賦值non-const auto_ptrs,卻不能拷貝和賦值const auto_ptrs. 我無法非常準确的理解這兩句話的意義,但根據我們觀察與試驗,應該可以這樣去理解:沒有這些代碼,我們本來就可以拷貝和賦值non-const的auto_ptr和禁止拷貝和賦值const的auto_ptr的功能,隻是無法拷貝和賦值臨時的auto_ptr(右值),而這些輔助代碼提供某些轉換,使我們可以拷貝和賦值臨時的auto_ptr,但并沒有使const的auto_ptr也能被拷貝和賦值。如下:
auto_ptr ap1 = auto_ptr(new int(0));
auto_ptr(new int(0))是一個臨時對象,一個右值,一般的拷貝構造函數當然能拷貝右值,因為其參數類别必須為一個const reference,但是我們知道,auto_ptr的拷貝函數其參數類型為reference,所以,為了使這行代碼能通過,我們引入auto_ptr_ref來實現從右值向左值的轉換。其過程為:
1) ap1要通過拷貝 auto_ptr(new int(0))來構造自己
2) auto_ptr(new int(0))作為右值與現有的兩個拷貝構造函數參數類型都無法匹配,也無法轉換成該種參數類型
3) 發現輔助的拷貝構造函數auto_ptr(auto_ptr_ref rhs) throw()
4) 試圖将auto_ptr(new int(0))轉換成auto_ptr_ref
5) 發現類型轉換函數operator auto_ptr_ref() throw(),轉換成功,從而拷貝成功。
從而通過一個間接類成功的實現了拷貝構造右值(臨時對象)
同時,這個輔助方法不會使const auto_ptr被拷貝,原因是在第5步,此類型轉換函數為non-const的,我們知道,const對象是無法調用non-const成員的,所以轉換失敗。當然,這裡有一個問題要注意,假設你把這些輔助轉換的代碼注釋掉,該行代碼還是可能成功編譯,這是為什麼呢?debug一下,我們可以發現隻調用了一次構造函數,而拷貝構造函數并沒有被調用,原因在于編譯器将代碼優化掉了。這種類型優化叫做returned value optimization,它可以有效防止一些無意義的臨時對象的構造。當然,前提是你的編譯器要支持returned value optimization。
c語言
auto被解釋為一個自動存儲變量的關鍵字,也就是申明一塊臨時的變量内存。
例如:
auto a = 3.7;
表示a為一個自動存儲的臨時變量。
C++語言
C++ 98标準/C++03标準
同C語言的意思完全一樣:auto被解釋為一個自動存儲變量的關鍵字,也就是申明一塊臨時的變量内存。
C++ 11标準
在C++11标準的語法中,auto被定義為自動推斷變量的類型。例如:
auto x=5.2;//這裡的x被auto推斷為double類型
map
for(auto it=m.begin();//這裡it被auto推斷為map
it!=m.end();++it)
{
//....
}
不過C++11的auto關鍵字時有一個限定條件,那就是必須給申明的變量賦予一個初始值,否則編譯器在編譯階段将會報錯。