C++ boost库学习之——Smart Pointers

智能指针是用来存储动态分配给对象的指针的对象,与C++内置的指针十分相似,但是智能指针能够在合适的时间自动删除对象,从而释放空间。智能指针在遇到异常时尤其有用,因为它能够确保动态分配对象的正确析构。同时,智能指针还可以对多个拥有者共享的动态分配对象进行跟踪。

智能指针可以抽象地认为是拥有指向对象的对象,因此当不再需要动态分配的对象时,智能指针会负责释放对象。

智能指针库提供六种智能指针类模板:

scoped_ptr<boost/scoped_ptr.hpp>拥有单个对象, 不能够被复制,即不希望被转让
scoped_array<boost/scoped_array.hpp>与scopt_ptr类似,不过是在堆上分配动态数组,弥补标准库中没有指向数组的智能指针缺憾.
shared_ptr<boost/shared_ptr.hpp>Object ownership shared among multiple pointers.
shared_array<boost/shared_array.hpp>Array ownership shared among multiple pointers.
weak_ptr<boost/weak_ptr.hpp>Non-owning observers of an object owned by shared_ptr.与shared_ptr配合使用,象旁观者一样观测资源的使用情况
intrusive_ptr<boost/intrusive_ptr.hpp>Shared ownership of objects with an embedded reference count.

这些模板继承并实现了std::auto_ptr模板。

智能指针保证了没有删除而引起的副作用。它的成员方法唯一扔出的异常是std::bad_alloc

  • scoped_ptr

scoped_ptr不能拷贝和赋是因为scoped_ptr同时把拷贝构造函数和操作符都声明为私有,保证了它管理的指针不能被转让所有权。但是可以使用成员函数swap()可以交换scoped_ptr保存的原始指针。使用scoped_ptr的好处:

1、使用简单,降低错误出现;

2、scoped_ptr实现简单,无冗余操作,安全并保证了效率,可以获得与原始指针同样的速度。

scoped_ptr的用法与auto_ptr的用法几乎一致,两者的相同点不能作为容器的元素,前者是因为不支持拷贝和赋值,不满足容器对元素类型的要求;后者是因为其转移语义,auto_ptr一旦赋值和拷贝将会失去其原有的功能;两者的不同点指针的所权:auto_ptr对指针的所有权可以转移,但是同一时刻只能有一个auto_ptr来管理指针。而scoped_ptr完全拒绝指针所有权的转让。

  • scoped_array

scoped_array是封装了new[]操作在堆上分配的动态数组。scoped_array特点是:

1、构造函数对应的指针对new[]的结果,与scoped_ptr对应的new的结果不同;

2、因为是new[]的结果,对应的是数组,所以该类没有重载单个指针对象的*和->运算符,而提供了[]操作重载,使得象数组一样用下标访问元素

3、由于其不是容器,没有提供begin(), end()等类似的迭代操作函数

4、scoped_array实现比较精简,只是一个纯粹的数组接口,其功能有限,不能动态增长,也没有迭代支持,不能与STL算法结合使用

5、scoped_array不提供数组索引的越界检查,如果使用了越界的索引将会引发定义行为。

  • shared_ptr

shared_ptr作为一个最象指针的智能指针,其功能跟内置指针十分相似,通过引用计算的方法,可以进行任意的赋值和拷贝,可以任何地方共享,在其引用计数为0时才会删除被包装的动态分配的对象。其作用有:

1、功能全,可以拷贝、赋值,还可以进行比较,即通过get()方法测试两个shared_ptr的相等或不等,以及提供了operator<比较大小

2、可以与STL标准关联容器(set和map)搭配使用;

3、提供转型的方法:static_pointer_cast<T>(),const_pointer_cast<T>(),dynamic_pointer_cast<T>(), 而不能用static_cast<T*>(),const_cast<T*>(),dynamic_cast<T*>()将shared_ptr对象转换,因为这将会导致转换后的对象不为shared_ptr类型;

4shared_ptr提供基本的线程安全保证,一个shared_ptr可以被多个线程安全读取

5、shared_ptr提供了一种传入删除器的构造函数,shared_ptr(Y *p, D d),其第一个参数为要被管理的指针,第二个删除器参数d则告诉shared_ptr在析构时不使用delete来操作指针p,而要用d来操作,即把delete p换成d(p)

6、shared_ptr<void>能够存储void*型的指针,而void*型指针可以指向任意类型,因此shared_ptr<void>就像时一个范型的指针容器,拥有容纳任意类型的能力。将void*类型可以通过static_pointer_cast<T>(),const_pointer_cast<T>(),dynamic_pointer_cast<T>()进行转型,不过代码安全性降低。

  • shared_array

shared_array可以认为时shared_ptrscoped_array的综合体,其注意事项与scoped_array一致。由于shared_ptrshared_ptrshared_ptrshared_ptrshared_ptr可以与STL配合使用,shared_array可以用shared_ptr<std::vector> 或者std:vector<shared_ptr>来代替。scoped_arrayscoped_array

  • weak_ptr

weak_ptr作为一个静静的观测者,被设计为与shared_ptr配合使用,获得资源的观测权,但不会共享资源,在构造和析构时不会产生指针引用计数的增加或减少。该智能指针的一个重要用途获得this指针的shared_ptr,使得对象自己能够生产shared_ptr管理自己:对象使用weak_ptr观测this指针,这并不影响计数,在需要的时候调用lock()函数,返回一个符合要求的shared_ptr供外界使用使用的注意事项有:

1、没有重载*和->操作这由于它不能操作资源,不共享指针;

2、其成员函数use_count()可以观测资源的引用计数,并一个expired()的功能等价于use_count()==0,但速度更快,用以判断观测的资源是否还存在;

3、当对象不存在,即expired()==true时,lock()函数将返回一个存储空指针的shared_ptr。

  • intrusive_ptr

intrusive_ptr是一个侵入式的引用计数型指针,用于以下两种情形:

1、对内存占用的要求非常严格,要求必须与原始指针一样;

2、现代代码已经有了引用计数机制管理的对象。

注意:boost库不推荐使用intrusive_ptr,如果真的有非常特别的需求而且在shared_ptr性能、开销等方面影响了程序的运行(一般不会出现),才开始考虑intrusive_ptr

上面说了这么多,咱们来些实际的代码把!

用途最广泛的shared_ptr为例:

简单的使用例子:

shared_ptr<double[1024]> p1( new double(1024) );
shared_ptr<double[]> p2( new double(n) );

可能内存泄漏的例子:

void f(shared_ptr<int>, int);
int g();

void ok()
{
    shared_ptr<int> p( new int(2) );
    f( p, g() );
}

void bad()
{
    f( shared_ptr<int>( new int(2) ), g() );
}


可以看到函数ok和bad的命名表明其函数的质量,ok遵守了shared_ptr的使用规则,而bad构造了一个临时的shared_ptr,从而允许了内存泄漏的可能性。因为函数的传参顺序不定,当new int(2)首先传参时,而g()可能传参时扔出异常,导致shared_ptr不会调用其析构函数,更多的信息参考:Herb Sutter's treatment。当然这种异常安全问题可以通过使用在boost/make_shared.hpp中的make_shared或者allocate_shared工厂函数来解决。

shared_ptr与vector和set配合使用的完整示例

#include <vector>
#include <set>
#include <iostream>
#include <algorithm>
#include <boost/shared_ptr.hpp>

struct Foo
{ 
  Foo( int _x ) : x(_x) {}
  ~Foo() { std::cout << "Destructing a Foo with x=" << x << "\n"; }
  int x;
  /* ... */
};

typedef boost::shared_ptr<Foo> FooPtr;

struct FooPtrOps
{
  bool operator()( const FooPtr & a, const FooPtr & b )
    { return a->x > b->x; }
  void operator()( const FooPtr & a )
    { std::cout << a->x << "\n"; }
};

int main()
{
  std::vector<FooPtr>         foo_vector;
  std::set<FooPtr,FooPtrOps>  foo_set; // NOT multiset!

  FooPtr foo_ptr( new Foo( 2 ) );
  foo_vector.push_back( foo_ptr );
  foo_set.insert( foo_ptr );

  foo_ptr.reset( new Foo( 1 ) );
  foo_vector.push_back( foo_ptr );
  foo_set.insert( foo_ptr );

  foo_ptr.reset( new Foo( 3 ) );
  foo_vector.push_back( foo_ptr );
  foo_set.insert( foo_ptr );

  foo_ptr.reset ( new Foo( 2 ) );
  foo_vector.push_back( foo_ptr );
  foo_set.insert( foo_ptr );

  std::cout << "foo_vector:\n";
  std::for_each( foo_vector.begin(), foo_vector.end(), FooPtrOps() );
  
  std::cout << "\nfoo_set:\n"; 
  std::for_each( foo_set.begin(), foo_set.end(), FooPtrOps() );
  std::cout << "\n";

//  Expected output:
//
//   foo_vector:
//   2
//   1
//   3
//   2
//   
//   foo_set:
//   3
//   2
//   1
//
//   Destructing a Foo with x=2
//   Destructing a Foo with x=1
//   Destructing a Foo with x=3
//   Destructing a Foo with x=2
   
  return 0;
}

线程安全:提供了与内置类型相同的线程安全,智能指针实例可以同时被多线程读不过仅仅是采用const 操作来获取。而不同的shared_ptr实例即使是在同一个实例的副本时并且共享相同的参考计数时,仍能够被多线程同时采用操作符=或者reset方法写。任何其他的同时访问导致未定义行为。例子:

shared_ptr<int> p(new int(42));


读安全例子

// thread A
shared_ptr<int> p2(p); // reads p

// thread B
shared_ptr<int> p3(p); // OK, multiple reads are safe

写安全例子

// thread A
p.reset(new int(1912)); // writes p

// thread B
p2.reset(); // OK, writes p2

写不安全例子

//--- Example 1 ---

// thread A
p = p3; // reads p3, writes p

// thread B
p3.reset(); // writes p3; undefined, simultaneous read/write

//--- Example 2 ---

// thread A
p3 = p2; // reads p2, writes p3

// thread B
// p2 goes out of scope: undefined, the destructor is considered a "write access"

//--- Example 3 ---

// thread A
p3.reset(new int(1));

// thread B
p3.reset(new int(2)); // undefined, multiple writes

更多关于智能指针如何使用的信息参考:

Smart Pointers


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值