C++ Boost 智能指针详解
(给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 -
关于 C++ Boost 智能指针,欢迎在评论中和我探讨。觉得文章不错,请点赞和在看支持我继续分享好文。谢谢!
关注『CPP开发者』
看精选C++技术文章 . 加C++开发者专属圈子
↓↓↓
点赞和在看就是最大的支持❤️