C++之智能指針初步及棄用auto_ptr的原因分析
RAII
使用局部對象來管理資源的技術

RAII的原理

RAII的四個步驟

裸指針存在的問題
delete后的指針變量就變成了一個失效指針(也叫作懸空指針)。
對于下面的代碼:
void Destroy(Object *op)
{
delete op;
delete[] op;
}
Object *op = new Object(10);
Object *arop = new Object[10];
Destroy(op);
Destroy(arop);
因此:

智能指針
智能指針的引入
智能指針是比原始指針更加智能的類,解決懸空指針多次刪除被指向對象,以及資源泄漏問題,通常用來確保指針的壽命和其指向對象的壽命一致。
智能指針雖然很智能,很容易被誤用,智能也是有代價的。
四種智能指針
auto_ptrunqiue_ptr(唯一性智能指針)shared_ptr(共享性智能指針)weak_ptr(管理弱引用)
其中后三個是C11支持,并且第一個已經被C11棄用。
C98中的auto_ptr所做的事情,就是動態(tài)分配對象以及當對象不再需要時自動執(zhí)行清理。
下面我們首先來了解一下為什么要將auto_ptr移除的原因:
因為該類型的智能指針意義不明確,使用淺拷貝方式時,兩個對象擁有同一塊資源:我們模仿源碼的邏輯
了解一下:比如下面的代碼:
class Object
{
int value;
public:
Object(int x = 0):value(x){cout<<"Create Object:"<<this<<endl;}
~Object(){cout<<"Destroy Object:"<<this<<endl;}
int & Value(){return value;}
const int& Value() const{return value;}
};
template<class _Ty>
class my_auto_ptr
{
private:
bool _Owns;//所有權
_Ty* _Ptr;
public:
my_auto_ptr(_Ty* p = NULL):_Owns(p != NULL),_Ptr(p){}
~my_auto_ptr()
{
if(_Owns)
{
delete _Ptr;
}
_Owns = false;
_Ptr = NULL;
}
_Ty* get() const
{
return _Ptr;
}
_Ty* operator->()const
{
return get();
}
_Ty & operator*()
{
return *get();
}
void reset(_Ty* p = NULL)
{
if(_Owns)
{
delete _Ptr;
}
_Ptr = p;
}
_Ty * release()const//編譯要通過,要么異變,要么強轉成普通指針
{
_Ty* tmp = NULL;
if(_Owns)
{
((my_auto_ptr*)this)->_Owns = false;
tmp = _Ptr;
((my_auto_ptr*)this)->_Ptr = NULL;
}
return tmp;
}
my_auto_ptr(const my_auto_ptr & op):_Owns(op._Owns)
{
if(_Owns)
{
_Ptr = op._Ptr;
}
}
};
void fun()
{
my_auto_ptr<Object> pobj(new Object(10));//pobj是my_auto_ptr類型
cout<<pobj->Value()<<endl;
cout<<(*pobj).Value()<<endl;//(*pobj)是Object的堆區(qū)對象。*(pobj._Ptr).Value()
}
int main()
{
my_auto_ptr<Object> pobja(new Object(10));
my_auto_ptr<Object> pobjb(pobja);
}
相關函數解釋:

此時程序必然會導致程序崩潰引發(fā)異常,主函數結束時對同一部分資源釋放了兩次,堆內存被釋放兩次

那么我們可能會考慮,將資源轉移,即修改拷貝構造如下:利用是釋放函數
my_auto_ptr(const my_auto_ptr & op):_Owns(op._Owns),_Ptr(op.release())
{}
看似好像解決了上面的問題,實則存在隱患

繼續(xù)來看:下面的代碼存在什么問題呢?
void fun(my_auto_ptr<Object> apx)
{
int x = apx->Value();
cout<<x<<endl;
}
int main()
{
my_auto_ptr<Object> pobja(new Object(10));
fun(pobja);
int a = pobja->Value();
cout<<a<<endl;
}
上述代碼的執(zhí)行邏輯如下:
pobja有兩個域擁有權域和指針域,拿pobja初始化形參apx時,會調動拷貝構造函數apx將自己的擁有權域設為1,調動release函數,銷毀了pobja對象的資源后,返回堆區(qū)對象的地址,apx接收后將自身的指針域指向原先pobja所指向的堆區(qū)對象fun函數結束,apx局部對象就會被析構,此時再打印a,對象其實已經不存在了并且自身早已失去了pobja的擁有權。
綜上,此時智能指針的拷貝構造函數的兩種寫法:
my_auto_ptr(const my_auto_ptr & op):_Owns(op._Owns)
{
if(_Owns)
{
_Ptr = op._Ptr;
}
}
my_auto_ptr(const my_auto_ptr & op):_Owns(op._Owns),_Ptr(op.release())
{}
- 第一種存在的問題:Object的資源會被兩個釋放兩次
- 第二種存在的問題:解決了第一種問題,但是不能解決類似于實參對象初始化形參時,實參之前自身的資源丟失的問題,找不著了,因為這種情況太過于隱蔽,容易出錯,所以auto_ptr作為函數參數傳遞時一定要避免的。或許你想到加上引用解決上面的問題,但是仔細思考后發(fā)現,我們并不知道函數對傳入的傳入的auto_ptr做了什么,如果當中的某些操作使其失去了對對象的所有權,那么這還可能會導致致命的執(zhí)行期錯誤。獲取再加上const 才是個不錯的選擇。
因此,C11標準之前的auto_ptr這個智能指針不被廣泛使用的原因就是:在某些應用場景下,拷貝構造函數的意義不明確,同理賦值語句也是這個道理,意義同樣不明確,因為C11標準之前并不存在移動賦值和移動構造的概念,還有就是之前談到的一個對象和一組對象的問題,對于自定義類型而言,auto_ptr的析構函數僅能夠析構一個對象,不能夠處理一組對象的情況,這些都是尚未解決的問題。
于是在C11中棄用,C17標準中直接移除。
歷史淵源:
在STL庫之前,有一個功能更加強大的boost庫,STL為了與其抗衡,應急制造了STL,但制作的不夠完善,由此因為STL未解決auto_ptr的問題,因此STl內的容器vector和list都不想和auto_ptr建立聯系。
總結
以上為個人經驗,希望能給大家一個參考,也希望大家多多支持腳本之家。
相關文章
c++連接mysql數據庫的兩種方法(ADO連接和mysql api連接)
現在正做一個接口,通過不同的連接字符串操作不同的數據庫。要用到mysql數據庫,C++連接mysql有2種方法:利用ADO連接、利用mysql自己的api函數進行連接,下面看看如何用吧2013-12-12

