文章目錄
1、前言
學習過C/C++的同學都知道,有一個非常方便又特別讓人煩的數據類型,那就是指針,不僅操作麻煩,還有各種“級別”,一級指針、二級指針、n級別指針…很多人從入門到放棄C/C++,很大一個原因是因爲搞不清楚指針、以及指針與其他對象之間千絲萬縷的關係。而在實際的開發過程中,經常會出現因爲未釋放申請的對象而導致內存溢出、程序奔潰。這裏就包括指針對象的釋放,那如果有一種指針,能申請對象後“自動”釋放,是不是很爽?本文將介紹Boost中提到的各種智能指針。
C++中出現智能是在1998年修訂的第一版C++標準中: std::auto_ptr 。 與下面boost中的其他智能指針相比,它就像是個普通的指針: 通過地址來訪問一個動態分配的對象。 它會在析構時調用 delete 操作符來自動釋放所包含的對象。 前提是在初始化的時候,傳給它一個由 new 操作符返回的對象地址。這就明顯減少了程序員手動delete的繁瑣操作。
另一方便,程序中頻繁出現的“異常”,也可以導致未正確釋放內存,而出現內存泄漏。而智能指針,可以在析構函數中準確釋放內存,避免出現內存泄漏。這也是智能指針的優點之一。 Boost C++ 庫 Smart Pointers 提供了許多可以用在各種場合的智能指針。
2、你知道RAII嗎?
習語 RAII(Resource Acquisition Is Initialization) :資源申請即初始化。智能指針是該習語重要的實現之一。 智能指針確保在任何情況下,動態分配的內存都能得到正確釋放,從而將開發人員從這項任務中解放出來。 這包括程序因爲異常而中斷,原本用於釋放內存的代碼被跳過的場景。 用一個動態分配的對象的地址來初始化智能指針,在析構的時候釋放內存,就確保了這一點。 因爲析構函數總是會被執行的,這樣所包含的內存也將總是會被釋放。
無論何時,一定得有第二條指令來釋放之前另一條指令所分配的資源時,RAII 都是適用的。 許多的 C++ 應用程序都需要動態管理內存,因而智能指針是一種很重要的 RAII 類型。 不過 RAII 本身是適用於許多其它場景的。
基於上述描述,如下代碼片段,模擬智能指針實現。
// Smart_ptr.hpp
#include <iostream>
using namespace std;
template<class T>
class Smart_ptr
{
public:
Smart_ptr(T *t)
{
cout << "Smart_ptr 構造函數" << endl;
this->t = t;
}
~Smart_ptr()
{
if (this->t != NULL)
{
cout << "Smart_ptr 西溝函數" << endl;
delete this->t;
}
}
T* &operator->()
{
return this->t;
}
private:
T *t;
};
構造Person測試類
#include <iostream>
#include <string>
using namespace std;
class Person
{
public:
Person(std::string name, int age) :name(name), age(age)
{
cout << "Person 的構造函數" << endl;
}
Person()
{
cout << "Person 的默認構造" << endl;
}
void operator=(const Person& p)
{
this->age = p.age;
this->name = p.name;
}
~Person()
{
cout << "Person 的析構函數" << endl;
}
void Show()
{
cout << this->name << "小朋友今年" << this->age << "歲了" << endl;
}
private:
std::string name;
int age;
};
測試代碼
#include"Smart_ptr.hpp"
#include"Person.hpp"
nt main()
{
Smart_ptr<Person> p = Smart_ptr<Person>(new Person("小花", 22));
p->Show();
return 0;
}
運行結果
從測試代碼中看出,我new了Person對象,在程序結束時並沒有delete,但是卻打印出了調用析構函數的日誌【不信你可以斷點調試這個程序,O(∩_∩)O哈哈~】。這就說明智能指針已經起作用了,幫你省掉了delete的步驟。是不是很開心^-^。
上述只是簡單地引入了一下智能指針的概念,以及描述了智能指針的作用,下面將着重介紹boost庫中提供的強大的智能指針。
3、作用域指針
3.1、概述
作用域指針類名爲: boost::scoped_ptr。 一個作用域指針獨佔一個動態分配的對象。它的定義在 boost/scoped_ptr.hpp 中。 不像 std::auto_ptr,一個作用域指針不能傳遞它所包含的對象到另一個作用域指針,也就是說無法通過拷貝構造傳參。 一旦用一個地址來初始化,這個動態分配的對象將在析構階段釋放。
一個作用域指針只獨佔一個內存地址,所以 boost::scoped_ptr 的實現就要比 std::auto_ptr 簡單。 在不需要拷貝構造傳遞的時候應該優先使用 boost::scoped_ptr 。
3.2、如何使用scoped_ptr
下面代碼片段,演示如何調用boost::scoped_ptr。其中Person對象貫穿全篇,定義在本文開頭處。
int main()
{
boost::scoped_ptr<Person> p(new Person("小紅", 12));
p->Show(); // 等價於 p.get()->Show();
//boost::scoped_ptr<Person> p1(p);; // 無法使用scoped_ptr拷貝構造定義初始化另一個對象
return 0;
}
執行結果
一經初始化,智能指針 boost::scoped_ptr 所包含的對象,可以通過類似於普通指針的接口來訪問。 這是因爲重載了相關的操作符 operator()*,operator->() 和 operator bool() 。 此外,還有 get() 和 reset() 方法。 前者返回所含對象的地址,後者用一個新的對象來重新初始化智能指針。 在這種情況下,新創建的對象賦值之前會先自動釋放所包含的對象。
boost::scoped_ptr 的析構函數中使用 delete 操作符來釋放所包含的對象。 這對 boost::scoped_ptr 所包含的類型加上了一條重要的限制。 即boost::scoped_ptr 不能用動態分配的數組來做初始化,因爲這需要調用 delete[] 來釋放。 在這種情況下,可以使用下面將要介紹的 boost:scoped_array 類。
3.3、scoped_ptr源碼分析
如下代碼片段是截取的boost::scoped_ptr源碼核心部分,相關解釋見註釋。
// scoped_ptr.hpp
#include <boost/config.hpp>
#include <boost/assert.hpp>
#include <boost/checked_delete.hpp>
#include <boost/smart_ptr/detail/sp_nullptr_t.hpp>
#include <boost/smart_ptr/detail/sp_disable_deprecated.hpp>
#include <boost/smart_ptr/detail/sp_noexcept.hpp>
#include <boost/detail/workaround.hpp>
#ifndef BOOST_NO_AUTO_PTR
# include <memory> // for std::auto_ptr
#endif
namespace boost
{
// scoped_ptr mimics a built-in pointer except that it guarantees deletion
// of the object pointed to, either on destruction of the scoped_ptr or via
// an explicit reset(). scoped_ptr is a simple solution for simple needs;
// use shared_ptr or std::auto_ptr if your needs are more complex.
template<class T> class scoped_ptr // noncopyable
{
private:
T * px; // 內部私有指針變量, 用於存儲初始化的對象。
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 ) BOOST_SP_NOEXCEPT : 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_SP_NOEXCEPT : px( p.release() )
{
#if defined(BOOST_SP_ENABLE_DEBUG_HOOKS)
boost::sp_scalar_constructor_hook( px );
#endif
}
#endif
// scoped_ptr 析構函數,可以看出調用了delete釋放對象內存
~scoped_ptr() BOOST_SP_NOEXCEPT
{
#if defined(BOOST_SP_ENABLE_DEBUG_HOOKS)
boost::sp_scalar_destructor_hook( px );
#endif
boost::checked_delete( px );
}
// 重置對象爲默認值,類似p=NULL
void reset(T * p = 0) BOOST_SP_NOEXCEPT_WITH_ASSERT
{
BOOST_ASSERT( p == 0 || p != px ); // catch self-reset errors
this_type(p).swap(*this);
}
T & operator*() const BOOST_SP_NOEXCEPT_WITH_ASSERT
{
BOOST_ASSERT( px != 0 );
return *px;
}
T * operator->() const BOOST_SP_NOEXCEPT_WITH_ASSERT
{
BOOST_ASSERT( px != 0 );
return px;
}
T * get() const BOOST_SP_NOEXCEPT
{
return px;
}
// implicit conversion to "bool"
#include <boost/smart_ptr/detail/operator_bool.hpp>
void swap(scoped_ptr & b) BOOST_SP_NOEXCEPT
{
T * tmp = b.px;
b.px = px;
px = tmp;
}
};
} // namespace boost
4、作用域數組
4.1、概述
作用域數組的使用方式與作用域指針相似。 不同點在於,作用域數組的析構函數使用 delete[] 操作符來釋放所包含的對象。 因爲該操作符只能用於數組對象,所以作用域數組必須通過動態分配的數組來初始化。作用域數組類名爲 boost::scoped_array。下面章節介紹如何使用作用域數組。
4.2、調用實例
#include<boost/scoped_array.hpp>
int main()
{
boost::scoped_array<int> i(new int[2]);
*i.get() = 1;
i[1] = 2;
i[0] = 1;
cout << i[0] << endl;
cout << i[1] << endl;
return 0;
}
運行結果
4.3、源碼分析
boost::acoped_array 核心源碼如下代碼段:
#include <boost/config.hpp>
#include <boost/assert.hpp>
#include <boost/checked_delete.hpp>
#include <boost/smart_ptr/detail/sp_nullptr_t.hpp>
#include <boost/smart_ptr/detail/sp_noexcept.hpp>
#include <boost/detail/workaround.hpp>
#include <cstddef> // for std::ptrdiff_t
namespace boost
{
// scoped_array extends scoped_ptr to arrays. Deletion of the array pointed to
// is guaranteed, either on destruction of the scoped_array or via an explicit
// reset(). Use shared_array or std::vector if your needs are more complex.
template<class T> class scoped_array // noncopyable
{
private:
T * px; //私有變量,存儲初始化對象
scoped_array(scoped_array const &); // 私有拷貝構造,和作用域指針類似,不允許拷貝構造
scoped_array & operator=(scoped_array const &); // 私有化=運算符
typedef scoped_array<T> this_type;
void operator==( scoped_array const& ) const;
void operator!=( scoped_array const& ) const;
public:
typedef T element_type;
explicit scoped_array( T * p = 0 ) BOOST_SP_NOEXCEPT : px( p )
{
#if defined(BOOST_SP_ENABLE_DEBUG_HOOKS)
boost::sp_array_constructor_hook( px );
#endif
}
~scoped_array() BOOST_SP_NOEXCEPT
{
#if defined(BOOST_SP_ENABLE_DEBUG_HOOKS)
boost::sp_array_destructor_hook( px );
#endif
boost::checked_array_delete( px );
}
void reset(T * p = 0) BOOST_SP_NOEXCEPT_WITH_ASSERT
{
BOOST_ASSERT( p == 0 || p != px ); // catch self-reset errors
this_type(p).swap(*this);
}
T & operator[](std::ptrdiff_t i) const BOOST_SP_NOEXCEPT_WITH_ASSERT
{
BOOST_ASSERT( px != 0 );
BOOST_ASSERT( i >= 0 );
return px[i];
}
T * get() const BOOST_SP_NOEXCEPT
{
return px;
}
// implicit conversion to "bool"
#include <boost/smart_ptr/detail/operator_bool.hpp>
void swap(scoped_array & b) BOOST_SP_NOEXCEPT
{
T * tmp = b.px;
b.px = px;
px = tmp;
}
};
5、共享指針
5.1、概述
共享指針是使用率最高的智能指針。 如果開發環境支持的話,可以使用 memory 中定義的 std::shared_ptr。 在 Boost C++ 庫裏,這個智能指針命名爲 boost::shared_ptr。
boost::shared_ptr 類似 boost::scoped_ptr。 不同之處在於 boost::shared_ptr 可以不獨佔一個對象。 它可以和其他 boost::shared_ptr 類型的智能指針共享所有權。 在這種情況下,引用對象的最後一個智能指針銷燬後,對象纔會被釋放。
5.2、使用示例
boost::shared_ptr開放拷貝構造,任何一個共享指針都可以被複制,這跟 boost::scoped_ptr 是不同的。如下示例代碼
#include<boost/shared_ptr.hpp>
#include<vector>
int main()
{
// 在標準容器中安全的使用動態分配的對象
std::vector<boost::shared_ptr<int>> v;
v.push_back(boost::shared_ptr<int>(new int(3)));
v.push_back(boost::shared_ptr<int>(new int(2)));
// 使用拷貝構造
boost::shared_ptr<int> p(new int(2));
boost::shared_ptr<int> p1(p);
p.reset(new int);
// 創建自定義對象共享指針
boost::shared_ptr<Person> pson;
pson->Show();
}
5.3、源碼分析
下面代碼片段截取部分核心源碼,註釋的方式講解共享指針核心功能。
template<class T> class shared_ptr
{
public:
// 構造函數
template<class Y>
explicit shared_ptr( Y * p ): px( p ), pn() // Y must be complete
{
boost::detail::sp_pointer_construct( this, p, pn );
}
// 拷貝構造函數
shared_ptr( shared_ptr const & r ) BOOST_SP_NOEXCEPT : px( r.px ), pn( r.pn )
{
}
// 使用weak_ptr構造
template<class Y>
explicit shared_ptr( weak_ptr<Y> const & r ): pn( r.pn ) // may throw
{
boost::detail::sp_assert_convertible< Y, T >();
// it is now safe to copy r.px, as pn(r.pn) did not throw
px = r.px;
}
// 拷貝構造
template< class Y >
shared_ptr( shared_ptr<Y> const & r, element_type * p ) BOOST_SP_NOEXCEPT : px( p ), pn( r.pn )
{
}
// 使用auto_ptr 構造
template<class Y>
explicit shared_ptr( std::auto_ptr<Y> & r ): px(r.get()), pn()
{
boost::detail::sp_assert_convertible< Y, T >();
Y * tmp = r.get();
pn = boost::detail::shared_count( r );
boost::detail::sp_deleter_construct( this, tmp );
}
// 使用unique_ptr構造
template< class Y, class D >
shared_ptr( std::unique_ptr< Y, D > && r ): px( r.get() ), pn()
{
boost::detail::sp_assert_convertible< Y, T >();
typename std::unique_ptr< Y, D >::pointer tmp = r.get();
if( tmp != 0 )
{
pn = boost::detail::shared_count( r );
boost::detail::sp_deleter_construct( this, tmp );
}
}
//重載=運算符
shared_ptr & operator=( shared_ptr const & r ) BOOST_SP_NOEXCEPT
{
this_type(r).swap(*this);
return *this;
}
//重載=運算符 ,可以實現a=b=c這種賦值操作
template<class Y, class D>
shared_ptr & operator=( std::unique_ptr<Y, D> && r )
{
this_type( static_cast< std::unique_ptr<Y, D> && >( r ) ).swap(*this);
return *this;
}
template<class Y, class D>
shared_ptr & operator=( boost::movelib::unique_ptr<Y, D> r )
{
// this_type( static_cast< unique_ptr<Y, D> && >( r ) ).swap( *this );
boost::detail::sp_assert_convertible< Y, T >();
typename boost::movelib::unique_ptr< Y, D >::pointer p = r.get();
shared_ptr tmp;
if( p != 0 )
{
tmp.px = p;
tmp.pn = boost::detail::shared_count( r );
boost::detail::sp_deleter_construct( &tmp, p );
}
tmp.swap( *this );
return *this;
}
//重置對象
void reset() BOOST_SP_NOEXCEPT
{
this_type().swap(*this);
}
//重載*運算符
typename boost::detail::sp_dereference< T >::type operator* () const BOOST_SP_NOEXCEPT_WITH_ASSERT
{
BOOST_ASSERT( px != 0 );
return *px;
}
//重載->運算符
typename boost::detail::sp_member_access< T >::type operator-> () const BOOST_SP_NOEXCEPT_WITH_ASSERT
{
BOOST_ASSERT( px != 0 );
return px;
}
//重載[]運算符
typename boost::detail::sp_array_access< T >::type operator[] ( std::ptrdiff_t i ) const BOOST_SP_NOEXCEPT_WITH_ASSERT
{
BOOST_ASSERT( px != 0 );
BOOST_ASSERT( i >= 0 && ( i < boost::detail::sp_extent< T >::value || boost::detail::sp_extent< T >::value == 0 ) );
return static_cast< typename boost::detail::sp_array_access< T >::type >( px[ i ] );
}
// 獲取對象實例
element_type * get() const BOOST_SP_NOEXCEPT
{
return px;
}
// 只有一個資源,返回true,如果有兩個或兩個以上資源,則返回false
bool unique() const BOOST_SP_NOEXCEPT
{
return pn.unique();
}
// 返回資源數
long use_count() const BOOST_SP_NOEXCEPT
{
return pn.use_count();
}
// 交換資源
void swap( shared_ptr & other ) BOOST_SP_NOEXCEPT
{
std::swap(px, other.px);
pn.swap(other.pn);
}
private:
template<class Y> friend class shared_ptr;
template<class Y> friend class weak_ptr;
element_type * px; // contained pointer
boost::detail::shared_count pn; // reference counter
}; // shared_ptr
從源碼可以看出,shared_ptr開放了拷貝構造、等號運算符等功能,使用上比前兩者更強大,同時支持多種智能指針構造,比如 weak_ptr 、auto_ptr 等。同時重載了指針相關的運算符,在用戶體驗上做的更加完美。
6、共享數組
6.1、概述
共享數組的行爲類似於共享指針。 關鍵不同在於共享數組在析構時,默認使用 delete[] 操作符來釋放所含的對象。 因爲這個操作符只能用於數組對象,共享數組必須通過動態分配的數組的地址來初始化。
共享數組對應的類型是 boost::shared_array。
6.2、使用示例
#include <boost/shared_array.hpp>
int main()
{
boost::shared_array<int> arr(new int[3]);
arr[0] = 1;
arr[1] = 2;
arr[2] = 3;
boost::shared_array<int> arr1(arr);
cout << arr1[0] << endl;
cout << arr1[1] << endl;
cout << arr1[2] << endl;
cout << "usr_count is :" << arr1.use_count() << endl;
if (arr1.unique())
{
cout << "unique" << endl;
}
else
{
cout << "not unique" << endl;
}
return 0;
}
運行結果
可以看出,boost::shared_array 與 boost::shared_ptr 神似,只是前者需要數組初始化,因爲析構的時候使用delete[]。
6.3、源碼分析
template<class T> class shared_array
{
public:
typedef T element_type;
// 默認構造函數
shared_array() BOOST_SP_NOEXCEPT : px( 0 ), pn()
{
}
// 有參構造函數
template<class Y>
explicit shared_array( Y * p ): px( p ), pn( p, checked_array_deleter<Y>() )
{
boost::detail::sp_assert_convertible< Y[], T[] >();
}
// 拷貝構造函數
template< class Y >
shared_array( shared_array<Y> const & r, element_type * p ) BOOST_SP_NOEXCEPT : px( p ), pn( r.pn )
{
}
// 重載=
shared_array & operator=( shared_array const & r ) BOOST_SP_NOEXCEPT
{
this_type( r ).swap( *this );
return *this;
}
// 重置指針對象
void reset() BOOST_SP_NOEXCEPT
{
this_type().swap( *this );
}
//重載[]
T & operator[] (std::ptrdiff_t i) const BOOST_SP_NOEXCEPT_WITH_ASSERT
{
BOOST_ASSERT(px != 0);
BOOST_ASSERT(i >= 0);
return px[i];
}
// 獲取指針本體
T * get() const BOOST_SP_NOEXCEPT
{
return px;
}
// 是否唯一數據源
bool unique() const BOOST_SP_NOEXCEPT
{
return pn.unique();
}
//
long use_count() const BOOST_SP_NOEXCEPT
{
return pn.use_count();
}
void swap(shared_array<T> & other) BOOST_SP_NOEXCEPT
{
std::swap(px, other.px);
pn.swap(other.pn);
}
private:
template<class Y> friend class shared_array;
T * px; // contained pointer
detail::shared_count pn; // reference counter 引用計數器
}; // shared_array
7、弱指針
7.1、概述
上述介紹的智能指針都能在不同的場景下獨立使用。 相反,弱指針只有在配合共享指針一起使用時纔有意義。 弱指針 boost::weak_ptr 的定義在 boost/weak_ptr.hpp 裏。boost::weak_ptr 一定是通過 boost::shared_ptr 來初始化的。一旦初始化之後,它基本上只提供一個有用的方法: lock()。此方法返回的初始化弱指針的共享指針。 如果這個共享指針不含有任何對象,返回的共享指針也將是空的。也就是這個方法返回初始化弱指針的指針對象。通過源碼也可以看出來,如下代碼段。
shared_ptr<T> lock() const BOOST_SP_NOEXCEPT
{
return shared_ptr<T>( *this, boost::detail::sp_nothrow_tag() );
}
當函數需要一個由共享指針所管理的對象,而這個對象的生存期又不依賴於這個函數時,就可以使用弱指針。 只要程序中還有一個共享指針掌管着這個對象,函數就可以使用該對象。 如果共享指針復位了,就算函數裏能得到一個共享指針,對象也不存在了。以下示例,使用boost官方提供的示例說明弱指針的使用。
7.2、使用示例
#include <windows.h>
#include <boost/shared_ptr.hpp>
#include <boost/weak_ptr.hpp>
#include <iostream>
DWORD WINAPI reset(LPVOID p)
{
boost::shared_ptr<int> *sh = static_cast<boost::shared_ptr<int>*>(p);
sh->reset();
return 0;
}
DWORD WINAPI print(LPVOID p)
{
boost::weak_ptr<int> *w = static_cast<boost::weak_ptr<int>*>(p);
boost::shared_ptr<int> sh = w->lock();
if (sh)
std::cout << *sh << std::endl;
return 0;
}
int main()
{
boost::shared_ptr<int> sh(new int(99));
boost::weak_ptr<int> w(sh);
HANDLE threads[2];
threads[0] = CreateThread(0, 0, reset, &sh, 0, 0);
threads[1] = CreateThread(0, 0, print, &w, 0, 0);
WaitForMultipleObjects(2, threads, TRUE, INFINITE);
}
main() 函數中,通過 Windows API 創建了2個線程。第一個線程函數 reset() 的參數是一個共享指針的地址。 第二個線程函數 print() 的參數是一個弱指針的地址。 這個弱指針是之前通過共享指針初始化的。一旦程序啓動之後,reset() 和 print() 都開始執行。 不過執行順序是不確定的。 這就導致了一個潛在的問題:reset() 線程在銷燬對象的時候 print() 線程可能正在訪問它。通過調用弱指針的 lock() 函數可以解決這個問題:如果對象存在,那麼 lock() 函數返回的共享指針指向這個合法的對象。否則,返回的共享指針被設置爲0,這等價於標準的null指針。
7.3、源碼分析
template<class T> class weak_ptr
{
public:
typedef typename boost::detail::sp_element< T >::type element_type;
// 構造函數
BOOST_CONSTEXPR weak_ptr() BOOST_SP_NOEXCEPT : px(0), pn()
{
}
BOOST_SP_NOEXCEPT : px( r.px ), pn( r.pn )
{
boost::detail::sp_assert_convertible< Y, T >();
}
// 有參構造函數,只能使用shared_ptr和weak_ptr初始化
template<class Y>
weak_ptr(shared_ptr<Y> const & r, element_type * p) BOOST_SP_NOEXCEPT: px( p ), pn( r.pn )
{
}
template<class Y> weak_ptr(weak_ptr<Y> const & r, element_type * p) BOOST_SP_NOEXCEPT: px( p ), pn( r.pn )
{
}
// lock函數,返回shared_ptr對象
shared_ptr<T> lock() const BOOST_SP_NOEXCEPT
{
return shared_ptr<T>( *this, boost::detail::sp_nothrow_tag() );
}
// 引用計數器爽
long use_count() const BOOST_SP_NOEXCEPT
{
return pn.use_count();
}
private:
element_type * px; // contained pointer
boost::detail::weak_count pn; // reference counter
}; // weak_ptr
通過源碼可以看出,boost::weak_ptr 只能使用 ==boost::shared_ptr==和 boost::weak_ptr 初始化。弱指針本身對於對象的生存期沒有任何影響。 lock() 返回一個共享指針,在多線程使用中是線程安全的。
8、介入式指針
8.1、概述
從上文可知, boost::shared_ptr 在內部記錄着引用到某個對象的共享指針的數量,但是對介入式指針來說卻沒有這一個功能,程序員需要自己做記錄。這對於開發框架對象來說特別有用,因爲它們需要記錄着自身被引用的次數。以下源碼使用 COM組件提供的函數做演示。
8.2、使用示例
#include <boost/intrusive_ptr.hpp>
#include <atlbase.h>
#include <iostream>
void intrusive_ptr_add_ref(IDispatch *p)
{
p->AddRef();
}
void intrusive_ptr_release(IDispatch *p)
{
p->Release();
}
void check_windows_folder()
{
CLSID clsid;
CLSIDFromProgID(CComBSTR("Scripting.FileSystemObject"), &clsid);
void *p;
CoCreateInstance(clsid, 0, CLSCTX_INPROC_SERVER, __uuidof(IDispatch), &p);
boost::intrusive_ptr<IDispatch> disp(static_cast<IDispatch*>(p));
CComDispatchDriver dd(disp.get());
CComVariant arg("C:\\Windows");
CComVariant ret(false);
dd.Invoke1(CComBSTR("FolderExists"), &arg, &ret);
std::cout << (ret.boolVal != 0) << std::endl;
}
void main()
{
CoInitialize(0);
check_windows_folder();
CoUninitialize();
}
COM 對象是使用 boost::intrusive_ptr 的絕佳範例,因爲 COM 對象需要記錄當前有多少指針引用它。 通過調用 AddRef() 和 Release() 函數,內部的引用計數分別增 1 或者減 1。當引用計數爲 0 時,COM 對象自動銷燬。
在 intrusive_ptr_add_ref() 和 intrusive_ptr_release() 內部調用 AddRef() 和 Release() 這兩個函數,來增加或減少相應 COM 對象的引用計數。 通過這個對象可以訪問底層的文件系統,比如檢查一個給定的目錄是否存在。 在上例中,我們檢查 C:\Windows 目錄是否存在。 關鍵點在於一旦介入式指針 disp 離開了它的作用域——check_windows_folder() 函數的末尾,函數 intrusive_ptr_release() 將會被自動調用。
8.3、源碼分析
template<class T> class intrusive_ptr
{
public:
typedef T element_type;
BOOST_CONSTEXPR intrusive_ptr() BOOST_SP_NOEXCEPT : px( 0 )
{
}
intrusive_ptr( T * p, bool add_ref = true ): px( p )
{
if( px != 0 && add_ref ) intrusive_ptr_add_ref( px );
}
intrusive_ptr(intrusive_ptr const & rhs): px( rhs.px )
{
if( px != 0 ) intrusive_ptr_add_ref( px );
}
~intrusive_ptr()
{
if( px != 0 ) intrusive_ptr_release( px );
}
private:
T * px;
};
通過源碼分析,boost::intrusive_ptr 無引用計數器,方便外部調用者自定義計數器使用。在其他地方和 boost::shared_ptr 無差別。
9、指針容器
9.1、概述
通過介紹Boost C++ 庫的各種智能指針之後,我們應該能夠編寫安全的代碼,來使用動態分配的對象和數組。如果需要將智能對象存儲在容器中,下面代碼段可以直接實現。
std::vector<boost::shared_ptr<int> > v;
v.push_back(boost::shared_ptr<int>(new int(1)));
v.push_back(boost::shared_ptr<int>(new int(2)));
上面例子中的代碼是正確的,智能指針確實可以這樣用,然而因爲某些原因,實際情況中並不這麼用。 第一,反覆聲明 boost::shared_ptr 需要更多的輸入。 其次,將 boost::shared_ptr 拷貝,需要頻繁的增加或者減少內部引用計數,降低效率。 由於這些原因,Boost C++ 庫提供了 指針容器 專門用來管理動態分配的對象。
9.2、使用示例
#include <boost/ptr_container/ptr_vector.hpp>
int main()
{
boost::ptr_vector<int> v;
v.push_back(new int(1));
v.push_back(new int(2));
}
boost::ptr_vector 與 boost::shared_ptr 初始化方式類似。== boost::ptr_vector== 用於動態分配的對象,它使用起來更容易也更高效。 boost::ptr_vector 獨佔它所包含的對象,因而容器之外的共享指針不能共享所有權,這跟 std::vector<boost::shared_ptr > 相反。
10、寫在結尾
本文是博主曾經學習 智能指針 時總結的筆記,由於時間比較久遠,可能部分功能沒有更詳細的講解,後續有時間會重新review,繼續完善。指針 作爲C/C++語言重要的特性之一,在使用中很容易出現疏忽釋放內存的現象,導致內存溢出。而博主之前接觸的如C#、Java等高級語言,幾乎不需要考慮釋放內存。這對於那些放棄C/C++,轉投“高級”語言的同學而言,是個巨大的藉口。但是,C++後來出現了 智能指針 ,很大程度上彌補了這一繁瑣的“短板”。在使用中也有種更“高級”語言的感覺。不管是什麼語言,只要學精通,結果都是差不多的,只是在使用上“仁者見仁智者見智”罷了。希望當初下決定學習C/C++的你我,不忘初心,繼續走好這條“無聊又刺激”的編程之路。
廢話不多說了,如果你在閱讀中發現本文有錯誤,請隨時聯繫博主修改完善,幫助他人,一起學習。如果對你有幫助,請點贊支持我,在編碼的路上,一起加油。
參考文檔