C++ -- 智慧指標( C++11與boost庫的智慧指標及其使用)
1 智慧指標的引入
2.但是有時候,我們new了,也delete了,但是還會出現問題。例如在new和delete之間呼叫了某個拋異常的函式,就有可能導致沒有執行delete。
例如:fun2裡面使用了new[],最後也使用了delete[],看著沒有問題,但是在new和delete之間呼叫了fun1,而fun1裡面拋了異常,但是在fun2的delete之前並沒有捕獲,就會導致delete沒有執行,仍然會有記憶體洩露的問題。
void fun1() { throw int(11); } void fun2() { int* p = new int[10000]; fun1(); delete[] p; } int main() { try { fun2(); } catch (int& e) { cout << "捕獲" << endl; } system("pause"); return 0; }
果想要解決上面的問題,有一種方法:就是如果發現delete之前發現某個函式丟擲了異常,就在delete之前捕獲這個異常,並且在catch語句裡面進行資源的釋放,並且可以再將這個異常重新丟擲。
void fun2() { int* p = new int[10000]; try { fun1(); } catch(int& e) { delete[] p; cout << "重新丟擲" << endl; throw; } delete[] p; }
但是這種方法寫著比較繁瑣。
4.智慧指標就有上面的作用,能夠自動的管理指標所指向的動態資源的釋放。它不僅有著RAII的思想還能夠像指標一樣。(RAII:分配資源即初始化,即建構函式分配資源和初始化資源,在解構函式清理資源。像指標一樣:能夠解引用)。
5.智慧指標實質上就是一個模板類,成員變數包含一個任意型別的指標,建構函式獲得資源並初始化,解構函式清理資源。
注意:智慧指標只能管理動態開闢的空間。
2 智慧指標的發展史
智慧指標都具有RAII的思想,即建構函式獲得資源,解構函式清理資源,但是當用一個智慧指標拷貝構造另一個智慧指標的時候,有可能會有淺拷貝的問題,這個空間會被釋放多次,智慧指標的發展就是圍繞著指標拷貝問題而走。
2.1 auto_ptr
(1)C++98裡面有一個智慧指標auto_ptr,對於拷貝構造和賦值運算子過載,該智慧指標採用管理權轉移的方式(當一個指標拷貝構造另一個指標時,當前指標就將對空間的管理權交給拷貝的那個指標,當前指標就指向空);
(2)但是這種方式不符合指標的要求(可以允許多個指標指向同一塊空間,將一個指標賦值給另一個指標的時候,就是需要讓兩個指標指向同一塊空間,而auto_ptr只允許一塊空間上只能有一個指標指向它),並且當管理權轉移之後要想再訪問之前的指標,就會出錯,因為之前的指標已經為NULL,就會出現解引用空指標的問題。
2.2 scoped_ptr/shared_ptr
因為auto_ptr有缺陷,但是C++標準裡面從C++98到C++11之間沒有出現新的智慧指標能解決這個缺陷,所以在這段時間內,boost這個官方組織就增加了智慧指標(scoped_ptr,shared_ptr,weak_ptr等)
(1)scoped_ptr採用防拷貝的方式(防拷貝就是不允許拷貝,拷貝就會出錯;防拷貝的實現:將拷貝構造和賦值運算子過載只宣告不實現,並且宣告為私有);
(2)shared_ptr為共享指標,裡面採用引用計數,當有shared_ptr指向同一塊空間的時候就增加引用計數,當引用計數減為0的時候才釋放該智慧指標管理的那塊空間。
(3)但是shared_ptr有一個缺點,就是會出現迴圈引用的問題(當一個shared_ptr(如sp1)管理的空間裡面包含一個shared_ptr的指標(_next),另一個shared_ptr(如sp2)管理的空間裡面也包含一個shared_ptr指標(_prev)時,當sp1->_next = sp2;sp2->_prev = sp1;此時就會使得sp1和sp2的引用計數都變為2,當出了這個作用域sp1和sp2的引用計數都會減為1,但是隻有引用計數為0時才會釋放管理的空間,就會使得sp1和sp2管理的空間沒有釋放。
(4)所以利用weak_ptr來解決迴圈引用的問題,weak_ptr叫弱指標,它主要是為了配合shared_ptr使用,用來解決迴圈引用的問題;
- 將會出現迴圈引用問題的指標用weak_ptr儲存著,weak_ptr並不擁有這塊空間,所以weak_ptr裡面不增加shared_ptr的引用計數,也不會釋放這塊空間。(注意weak_ptr裡面也有自己的引用計數)
(5)boost庫裡面還包含scoped_array和shared_array(這個適用於delete[]的場景)
2.3 C++11(unique_ptr和shared_ptr)
(1)C++11借鑑了boost庫裡面的智慧指標(C++對應的智慧指標位於標頭檔案<memory>
裡。
C++11裡面的unique_ptr就是boost庫裡面的scoped_ptr(防拷貝);
C++11裡面的shared_ptr就是boost裡面的shared_ptr。
(2)C++11裡面不包含類似於scoped_array和shared_array,而它採用定製刪除器的方式管理空間的釋放。
- 定製刪除器就是自己指定採用何種方式釋放該空間(delete/free或其它);
- 因為在實現智慧指標的過程中,我們需要管理資料的構造和析構,但不同的資料有不同的析構方式,就需要自己指定刪除方式。(例如new出來的資料,就必須用delete,而new[ ]就需要delete[ ]等)。注意:boost庫裡面也包含定製刪除器。
3 auto_ptr
1.原始碼分析:
template<class _Ty>
class auto_ptr
{
public:
typedef auto_ptr<_Ty> _Myt;
typedef _Ty element_type;
//建構函式
explicit auto_ptr(_Ty *_Ptr = 0) _THROW0()
: _Myptr(_Ptr)
{}
//拷貝建構函式,release返回儲存_Right指向空間的臨時變數,則_Myptr管理_Right管理的空間,並且將_Right置為空
auto_ptr(_Myt& _Right) _THROW0()
: _Myptr(_Right.release())
{}
//賦值運算子過載,_Right.release()將_Right的管理權轉移給_Myptr,將_Right置為空。reset裡面判斷是不是自己給自己賦值,不是就釋放_Myptr,並且讓_Myptr管理_Right以前管理的個指標,再返回*this。
_Myt& operator=(_Myt& _Right) _THROW0()
{
reset(_Right.release());
return (*this);
}
//解構函式釋放_myptr
~auto_ptr() _NOEXCEPT
{
delete _Myptr;
}
_Ty& operator*() const _THROW0()
{
#if _ITERATOR_DEBUG_LEVEL == 2
if (_Myptr == 0)
_DEBUG_ERROR("auto_ptr not dereferencable");
#endif
return (*get());
}
_Ty *operator->() const _THROW0()
{
#if _ITERATOR_DEBUG_LEVEL == 2
if (_Myptr == 0)
_DEBUG_ERROR("auto_ptr not dereferencable");
#endif
return (get());
}
//get返回原生指標
_Ty *get() const _THROW0()
{
return (_Myptr);
}
//release用於管理權的轉移,將_myptr儲存在tmp裡面,將_mytmp的指標置為空,再返回tmp
_Ty *release() _THROW0()
{ // return wrapped pointer and give up ownership
_Ty *_Tmp = _Myptr;
_Myptr = 0;
return (_Tmp);
}
void reset(_Ty *_Ptr = 0)
{
if (_Ptr != _Myptr)
delete _Myptr;
_Myptr = _Ptr;
}
private:
_Ty *_Myptr; // the wrapped object pointer
};
2.使用:(必須包含標頭檔案<memory>
)
將動態開闢的指標交給一個智慧指標。
void Test_auto_ptr()
{
auto_ptr<int> ap1(new int(10));
cout << *ap1 << endl; //輸出10
auto_ptr<int> ap2(ap1);
cout << *ap2 << endl; //輸出10
cout << *ap1 << endl; //此時ap1為NULL,就是解引用空指標,程式崩潰
}
scoped_ptr
1.原始碼:
template<class T>
class scoped_ptr
{
private:
T * px; //只含有一個成員變數T*的指標
//將拷貝構造與賦值運算子過載宣告為私有,且不實現
scoped_ptr(scoped_ptr const &);
scoped_ptr & operator=(scoped_ptr const &);
typedef scoped_ptr<T> this_type;
void operator==( scoped_ptr const& ) const;
void operator!=( scoped_ptr const& ) const;
public:
typedef T element_type;
//建構函式
explicit scoped_ptr( T * p = 0 )
: px( p )
{
#if defined(BOOST_SP_ENABLE_DEBUG_HOOKS)
boost::sp_scalar_constructor_hook( px );
#endif
}
#ifndef BOOST_NO_AUTO_PTR
explicit scoped_ptr( std::auto_ptr<T> p ) BOOST_NOEXCEPT
: px( p.release() )
{
#if defined(BOOST_SP_ENABLE_DEBUG_HOOKS)
boost::sp_scalar_constructor_hook( px );
#endif
}
#endif
//解構函式
~scoped_ptr()
{
#if defined(BOOST_SP_ENABLE_DEBUG_HOOKS)
boost::sp_scalar_destructor_hook( px );
#endif
boost::checked_delete( px );
}
void reset(T * p = 0)
{
BOOST_ASSERT( p == 0 || p != px );
this_type(p).swap(*this);
}
T & operator*() const
{
BOOST_ASSERT( px != 0 );
return *px;
}
T * operator->() const
{
BOOST_ASSERT( px != 0 );
return px;
}
//獲得原生指標
T * get() const BOOST_NOEXCEPT
{
return px;
}
#include <boost/smart_ptr/detail/operator_bool.hpp>
void swap(scoped_ptr & b) BOOST_NOEXCEPT
{
T * tmp = b.px;
b.px = px;
px = tmp;
}
2.使用:
當使用boost庫的時候,必須先要從boost官方網站下載boost的原始碼,然後將從boost庫下載的原始碼所在目錄包含至自己的專案,並且使用時要指定名稱空間為boost。
#include<iostream>
#include<string>
#include<boost/scoped_ptr.hpp>
#include<boost/scoped_array.hpp>
int main()
{
boost::scoped_ptr<int> sp1(new int(10));
//boost::scoped_ptr<int> sp2(sp1); //不能拷貝
boost::scoped_array<std::string> sp2(new std::string[10]);
return 0;
}
5 boost庫shared_ptr的使用
void Test_boost_shared_ptr()
{
boost::shared_ptr<int> sp1(new int(10));
std::cout << *sp1 << std::endl; //輸出10
boost::shared_ptr<int> sp2(sp1);
std::cout << *sp2 << std::endl; //輸出10
boost::shared_array<std::string> sp3(new std::string[10]);
sp3[5] = "111"; //shared_array裡面過載了[],所以可以採用下標的方式進行讀寫
boost::shared_array<std::string> sp4(sp3);
std::cout << sp4[5] << std::endl; //輸出111
}
6 C++11裡shared_ptr的使用
1.基本使用與boost裡shared_ptr的使用方式一致
void Test_Shared_ptr()
{
std::shared_ptr<int> sp1(new int(20));
std::shared_ptr<int> sp2(sp1);
std::cout << *sp2 << std::endl;
}
2.由於C++11裡面沒有shared_array或scoped_array之類智慧指標,所以只能用shared_ptr需要自己定製刪除器:
(1)首先自己定製刪除器(例如我定製了一個delete和一個delete[ ])
自己需要編寫相應的仿函式,在用shared_ptr時不用將自己編寫的刪除器作為模板引數,但是在構造shared_ptr物件時需要傳相應仿函式的物件。
template<class T>
struct Delete
{
void operator()(T* ptr)
{
delete ptr;
}
};
template<class T>
struct DeleteArray
{
void operator()(T* ptr)
{
delete[] ptr;
}
};
(2)使用:
void Test_Shared_ptr()
{
std::shared_ptr<int> sp1(new int(20));
std::shared_ptr<int> sp2(sp1);
std::cout << *sp2 << std::endl;
DeleteArray<std::string> da;
std::shared_ptr<std::string> sp3(new std::string[10],da);
}
--------------------- 本文來自 Nicole xu 的CSDN 部落格 ,全文地址請點選:https://blog.csdn.net/xu1105775448/article/details/80625936?utm_source=copy