查看原文
其他

C++ Boost 智能指针详解

CPP开发者 2021-07-20

(给CPP开发者加星标,提升C/C++技能)

来源:造轮子
https://blog.csdn.net/Marble_ccp/article/details/105606236

前言


学习过C/C++的同学都知道,有一个非常方便又特别让人烦的数据类型,那就是指针,不仅操作麻烦,还有各种“级别”,一级指针、二级指针、n级别指针…很多人从入门到放弃C/C++,很大一个原因是因为搞不清楚指针、以及指针与其他对象之间千丝万缕的关系。而在实际的开发过程中,经常会出现因为未释放申请的对象而导致内存溢出、程序奔溃。这里就包括指针对象的释放,那如果有一种指针,能申请对象后“自动”释放,是不是很爽?本文将介绍Boost中提到的各种智能指针。


C++中出现智能是在1998年修订的第一版C++标准中:std::auto_ptr 。与下面boost中的其他智能指针相比,它就像是个普通的指针:通过地址来访问一个动态分配的对象。它会在析构时调用 delete 操作符来自动释放所包含的对象。前提是在初始化的时候,传给它一个由 new 操作符返回的对象地址。这就明显减少了程序员手动delete的繁琐操作。


另一方便,程序中频繁出现的“异常”,也可以导致未正确释放内存,而出现内存泄漏。而智能指针,可以在析构函数中准确释放内存,避免出现内存泄漏。这也是智能指针的优点之一。Boost C++ 库 Smart Pointers 提供了许多可以用在各种场合的智能指针。


你知道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"
int 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 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++的你我,不忘初心,继续走好这条“无聊又刺激”的编程之路。


废话不多说了,如果你在阅读中发现本文有错误,请随时联系博主修改完善,帮助他人,一起学习。如果对你有帮助,请点赞支持我,在编码的路上,一起加油。


参考文档


http://theboostcpplibraries.com/


- EOF -


推荐阅读  点击标题可跳转

1、一文带你轻松掌握多种C++编程范式

2、lib 和 dll 的区别、生成以及使用详解

3、C++ typeid关键字详解


关于 C++ Boost 智能指针,欢迎在评论中和我探讨。觉得文章不错,请点赞和在看支持我继续分享好文。谢谢!


关注『CPP开发者』

看精选C++技术文章 . 加C++开发者专属圈子

↓↓↓


点赞和在看就是最大的支持❤️

    您可能也对以下帖子感兴趣

    文章有问题?点此查看未经处理的缓存