Boost智能指针

Boost 只能指针常用的有四种,分别为:shared_ptr、weak_ptr、auto_ptr以及scoped_ptr指针。

shared_ptr指针

shared_ptr指针就是所谓的只能计数指针,用来管理非栈上的内存指针。它可以从一个裸指针、另一个shared_ptr、一个auto_ptr、或者一个weak_ptr构造。还可以传递第二个参数给shared_ptr的构造函数,它被称为删除器(deleter)。删除器用于处理共享资源的释放,这对于管理那些不是用new分配也不是用delete释放的资源时非常有用。shared_ptr被创建后,就可以像普通指针一样使用了,除了一点,它不能被显式地删除。

shared_ptr指针的使用如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
{
shared_ptr<int> pInt1;
assert(pInt1.use_count() == 0); // 还没有引用指针
{
shared_ptr<int> pInt2(new int(5));
assert(pInt2.use_count() == 1); // new int(5)这个指针被引用1次

pInt1 = pInt2;
assert(pInt2.use_count() == 2); // new int(5)这个指针被引用2次
assert(pInt1.use_count() == 2);
} //pInt2离开作用域, 所以new int(5)被引用次数-1

assert(pInt1.use_count() == 1);
} // pInt1离开作用域,引用次数-1,现在new int(5)被引用0次,所以销毁它

如果资源的创建销毁不是以new和delete的方式进行的,该怎么办呢?通过前面的接口可以看到,shared_ptr的构造函数中可以指定删除器。示例代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class FileCloser
{
public:
void operator()(FILE \*pf)
{

if (pf != NULL)
{
fclose(pf);
pf = NULL;
}
}
};

shared_ptr<FILE> fp(fopen(pszConfigFile, "r"), FileCloser());

在使用shared_ptr时,需要避免同一个对象指针被两次当成shard_ptr构造函数里的参数的情况。考虑如下代码:

1
2
3
4
5
6
7
{
int *pInt = new int(5);
shared_ptr<int> temp1(pInt);
assert(temp1.use_count() == 1);
shared_ptr<int> temp2(pInt);
assert(temp2.use_count() == 1);
} // temp1和temp2都离开作用域,它们都销毁pInt,会导致两次释放同一块内存

正确的做法是将原始指针赋给智能指针后,以后的操作都要针对智能指针了。参考代码如下:

1
2
3
4
5
6
{
shared_ptr<int> temp1(new int(5));
assert(temp1.use_count() == 1);
shared_ptr<int> temp2(temp1);
assert(temp2.use_count() == 2);
} // temp1和temp2都离开作用域,引用次数变为0,指针被销毁。

另外,使用shared_ptr来包装this时,也会产生与上面类似的问题。考虑如下代码:

1
2
3
4
5
6
7
8
9
10
11
class A
{
public:
shared_ptr<A> Get()
{
return shared_ptr<A>(this);
}
}

shared_ptr<A> pA(new A());
shared_ptr<A> pB = pA->Get();

当pA和pB离开作用域时,会将堆上的对象释放两次。如何解决上述问题呢?C++ 11提供了如下机制:将类从enable_shared_from_this类派生,获取shared_ptr时使用shared_from_this接口。参考代码如下:

1
2
3
4
5
6
7
8
class A :public enable_shared_from_this<A>
{
public:
shared_ptr<A> Get()
{
return shared_from_this();
}
}

在多线程中使用shared_ptr时,如果存在拷贝或赋值操作,可能会由于同时访问引用计数而导致计数无效。解决方法是向每个线程中传递公共的week_ptr,线程中需要使用shared_ptr时,将week_ptr转换成shared_ptr即可。
以上例子来自这里

weak_ptr指针

weak_ptr是为配合shared_ptr而引入的一种智能指针,它更像是shared_ptr的一个助手,而不是智能指针,因为它不具有普通指针的行为,没有重载operator*和operator->,它的最大作用在于协助shared_ptr,像旁观者那样观测资源的使用情况。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
template<class T> class weak_ptr{  
public:
weak_ptr();
template<class Y> weak_ptr(shared_ptr<Y> const & r);
weak_ptr(weak_ptr const & r);

~weak_ptr();
weak_ptr & operator=(weak_ptr const &r);

long use_count() const;
bool expired() const;
shared_ptr<T> lock() const;

void reset();
void swap(weak_ptr<T> &b);
};

weak_ptr是一个“弱”指针,但它能够完成一些特殊的工作,足以证明它的存在价值。

weak_ptr被设计为与shared_ptr共同工作,可以从一个shared_ptr或者另一个weak_ptr对象构造,获得资源的观测权。但weak_ptr没有共享资源,它的构造不会引起指针引用计数的增加。同样,在weak_ptr析构时也不会导致引用计数的减少,它只是一个静静地观察者。

使用weak_ptr的成员函数use_count()可以观测资源的引用计数,另一个成员函数expired()的功能等价于use_count() == 0,但更快,表示观测的资源(也就是shared_ptr管理的资源)已经不复存在了。

weak_ptr 没有重载operator*和->,这是特意的,因为它不共享指针,不能操作资源,这是它弱的原因。但它可以使用一个非常重要的成员函数lock()从被观测的shared_ptr获得一个可用的shared_ptr对象,从而操作资源。当expired() == true的时候,lock()函数将返回一个存储空指针的shared_ptr。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
int main(){  
shared_ptr<int> sp(new int(10));
assert(sp.use_count() == 1);
//create a weak_ptr from shared_ptr
weak_ptr<int> wp(sp);
//not increase the use count
assert(sp.use_count() == 1);
//judge wp is invalid
//expired() is equivalent with use_count() == 0
if(!wp.expired()){
shared_ptr<int> sp2 = wp.lock();//get a shared_ptr
\*sp2 = 100;
assert(wp.use_count() == 2);
cout << \*sp2 << endl;
}//out of scope,sp2 destruct automatically,use_count()--;
assert(wp.use_count() == 1);
sp.reset();//shared_ptr is invalid
assert(wp.expired());
assert(!wp.lock());
}

获得this的shared_ptr

weak_ptr的一个重要用途是获得this指针的shared_ptr,使对象自己能够生产shared_ptr管理自己:对象使用weak_ptr观测this指,这并不影响引用计数,在需要的时候就调用lock()函数,返回一个符合要求的shared_ptr使外界使用。

这个解决方案被实现为一个惯用法,在头文件定义了一个助手类enable_shared_from_this,其声明如下:

1
2
3
4
5
6
template<class T>  
class enable_shared_from_this
{
public:
shared_ptr<T> shared_from_this();
}

使用的时候只需要让想被shared_ptr管理的类从它继承即可,成员函数shared_from_this()会返回this的shared_ptr

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#include <iostream>  
#include <boost/smart_ptr.hpp>
#include <boost/enable_shared_from_this.hpp>
#include <boost/make_shared.hpp>
using namespace boost;
using namespace std;
class self_shared:
public enable_shared_from_this<self_shared>{
public:
self_shared(int n):x(n){}
int x;
void print(){
cout << "self_shared:" << x << endl;
}
};
int main(){
shared_ptr<self_shared> sp =
make_shared<self_shared>(315);
sp->print();
shared_ptr<self_shared> p = sp->shared_from_this();
p->x = 100;
p->print();

}

运行结果:

1
2
self_shared:315
self_shared:100

需要注意的是千万不能从一个普通对象(非shared_ptr)使用shared_from_this ()获取shared_ptr,如

1
2
3
self_shared ss;

shaerd_ptr<self_shared> p = ss.shared_from_this();//error

这样虽然语法上能通过,编译也无问题,但在运行时会导致shared_ptr析构时企图删除一个栈上分配的对象,发生未定义行为。

以上内容来自这里

auto_ptr指针

auto_ptr通过在栈上构建一个对象a,对象a中wrap了动态分配内存的指针p,所有对指针p的操作都转为对对象a的操作。而在a的析构函数中会自动释放p的空间,而该析构函数是编译器自动调用的,无需程序员操心。

1
2
3
4
5
6
7
8
9
10
11
12
用法一:  
std::auto_ptr<MyClass>m_example(new MyClass());

用法二:
std::auto_ptr<MyClass>m_example;
m_example.reset(new MyClass());

用法三(指针的赋值操作):
std::auto_ptr<MyClass>m_example1(new MyClass());
std::auto_ptr<MyClass>m_example2(new MyClass());
m_example2=m_example1;
```

对于上面的代码:m_example2=m_example1; 则C++会把m_example所指向的内存回收,使m_example1 的值为NULL,所以在C++中,应绝对避免把auto_ptr放到容器中。即应避免下列代码:
vector>m_example;
当用算法对容器操作的时候,你很难避免STL内部对容器中的元素实现赋值传递,这样便会使容器中多个元素被置位NULL,而这不是我们想看到的。

示例:

1
2
3
4
5
6
7
// 示例1(a):原始代码    
void f()
{

T* pt( new T );
/*...更多的代码...*/
delete pt;
}

以上代码需要手动的delete掉堆上的内存,如果使用auto_ptr指针的话,就不需要:

1
2
3
4
5
6
// 示例1(b):安全代码,使用了auto_ptr  
void f()
{

auto_ptr<T> pt( new T );
/*...更多的代码...*/
} // 酷:当pt出了作用域时析构函数被调用,从而对象被自动删除

使用一个auto_ptr就像使用一个内建的指针一样容易,而且如果想要“撤销”资源,重新采用手动的所有权,我们只要调用release()。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// 示例2:使用一个auto_ptr  
void g()
{

// 现在,我们有了一个分配好的对象
T* pt1 = new T;
// 将所有权传给了一个auto_ptr对象
auto_ptr<T> pt2(pt1);
// 使用auto_ptr就像我们以前使用简单指针一样,
*pt2 = 12; // 就像*pt1 = 12
pt2->SomeFunc(); // 就像pt1->SomeFunc();
// 用get()来获得指针的值
assert( pt1 == pt2.get() );
// 用release()来撤销所有权
T* pt3 = pt2.release();
// 自己删除这个对象,因为现在没有任何auto_ptr拥有这个对象
delete pt3;
} // pt2不再拥有任何指针,所以不要试图删除它...OK,不要重复删除

```

我们可以使用auto_ptr的reset()函数来重置auto_ptr使之拥有另一个对象。如果这个auto_ptr已经拥有了一个对象,那么,它会先删除已经拥有的对象,因此调用reset()就如同销毁这个auto_ptr,然后新建一个并拥有一个新对象:

1
2
3
4
5
6
// 示例 3:使用reset()  
void h()
{

auto_ptr<T> pt( new T(1) );
pt.reset( new T(2) ); // 删除由"new T(1)"分配出来的第一个T
} // 最后pt出了作用域,第二个T也被删除了

以上原文来自这里

scoped_ptr指针

scoped_ptr和std::auto_ptr非常类似,是一个简单的智能指针,它能够保证在离开作用域后对象被自动释放。下列代码演示了该指针的基本应用:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#include <string>
#include <iostream>
#include <boost/scoped_ptr.hpp>

class implementation
{
public:
~implementation() { std::cout <<"destroying implementation\n"; }
void do_something() { std::cout << "did something\n"; }
};

void test()
{

boost::scoped_ptr<implementation> impl(new implementation());
impl->do_something();
}

void main()
{

std::cout<<"Test Begin ... \n";
test();
std::cout<<"Test End.\n";
}

该代码的输出结果是:

1
2
3
4
Test Begin ...
did something
destroying implementation
Test End.

可以看到:当implementation类离其开impl作用域的时候,会被自动删除,这样就会避免由于忘记手动调用delete而造成内存泄漏了。

scoped_ptr的实现和std::auto_ptr非常类似,都是利用了一个栈上的对象去管理一个堆上的对象,从而使得堆上的对象随着栈上的对象销毁时自动删除。不同的是,boost::scoped_ptr有着更严格的使用限制——不能拷贝。这就意味着:boost::scoped_ptr指针是不能转换其所有权的。

  1. 不能转换所有权
    boost::scoped_ptr所管理的对象生命周期仅仅局限于一个区间(该指针所在的”{}”之间),无法传到区间之外,这就意味着boost::scoped_ptr对象是不能作为函数的返回值的(std::auto_ptr可以)。

  2. 不能共享所有权
    这点和std::auto_ptr类似。这个特点一方面使得该指针简单易用。另一方面也造成了功能的薄弱——不能用于stl的容器中。

  3. 不能用于管理数组对象
    由于boost::scoped_ptr是通过delete来删除所管理对象的,而数组对象必须通过deletep[]来删除,因此boost::scoped_ptr是不能管理数组对象的,如果要管理数组对象需要使用boost::scoped_array类。

scoped_ptr的定义:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
namespace boost {

template<typename T> class scoped_ptr : noncopyable {
public:
explicit scoped_ptr(T* p = 0);
~scoped_ptr();

void reset(T* p = 0);

T& operator*() const;
T* operator->() const;
T* get() const;

void swap(scoped_ptr& b);
};

template<typename T>
void swap(scoped_ptr<T> & a, scoped_ptr<T> & b);
}
```

示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
#include <string>
#include <iostream>

#include <boost/scoped_ptr.hpp>
#include <boost/scoped_array.hpp>

#include <boost/config.hpp>
#include <boost/detail/lightweight_test.hpp>

void test()
{

// test scoped_ptr with a built-in type
long * lp = new long;
boost::scoped_ptr<long> sp ( lp );
BOOST_TEST( sp.get() == lp );
BOOST_TEST( lp == sp.get() );
BOOST_TEST( &\*sp == lp );

\*sp = 1234568901L;
BOOST_TEST( \*sp == 1234568901L );
BOOST_TEST( \*lp == 1234568901L );

long * lp2 = new long;
boost::scoped_ptr<long> sp2 ( lp2 );

sp.swap(sp2);
BOOST_TEST( sp.get() == lp2 );
BOOST_TEST( sp2.get() == lp );

sp.reset(NULL);
BOOST_TEST( sp.get() == NULL );

}

void main()
{

test();
}

boost::scoped_ptr和std::auto_ptr的选取:

boost::scoped_ptr和std::auto_ptr的功能和操作都非常类似,如何在他们之间选取取决于是否需要转移所管理的对象的所有权(如是否需要作为函数的返回值)。如果没有这个需要的话,大可以使用boost::scoped_ptr,让编译器来进行更严格的检查,来发现一些不正确的赋值操作。

以上原文来自这里