Boost中的智能指針詳細總結

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_ptrauto_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_arrayboost::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_vectorboost::shared_ptr 初始化方式類似。== boost::ptr_vector== 用於動態分配的對象,它使用起來更容易也更高效。 boost::ptr_vector 獨佔它所包含的對象,因而容器之外的共享指針不能共享所有權,這跟 std::vector<boost::shared_ptr > 相反。

10、寫在結尾

本文是博主曾經學習 智能指針 時總結的筆記,由於時間比較久遠,可能部分功能沒有更詳細的講解,後續有時間會重新review,繼續完善。指針 作爲C/C++語言重要的特性之一,在使用中很容易出現疏忽釋放內存的現象,導致內存溢出。而博主之前接觸的如C#、Java等高級語言,幾乎不需要考慮釋放內存。這對於那些放棄C/C++,轉投“高級”語言的同學而言,是個巨大的藉口。但是,C++後來出現了 智能指針 ,很大程度上彌補了這一繁瑣的“短板”。在使用中也有種更“高級”語言的感覺。不管是什麼語言,只要學精通,結果都是差不多的,只是在使用上“仁者見仁智者見智”罷了。希望當初下決定學習C/C++的你我,不忘初心,繼續走好這條“無聊又刺激”的編程之路。

廢話不多說了,如果你在閱讀中發現本文有錯誤,請隨時聯繫博主修改完善,幫助他人,一起學習。如果對你有幫助,請點贊支持我,在編碼的路上,一起加油。

參考文檔

http://theboostcpplibraries.com/

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章