一、smart_ptr库概述
new、delete以及指针的不恰当运用是c++中造成资源获取、释放问题的根源,智能指针可以在退出作用域时,不管是正常流程离开或是因异常离开,总调用delete来析构在堆上动态分配的对象。
boost.smart_ptr库提供了六种智能指针,shared_ptr、weak_ptr、scoped_ptr、scoped_array、shared_array、intrusive_ptr。它们都是轻量级的对象,速度与原始指针相差无几,都是异常安全的(exception_safe),而且对于所指的类型T唯一的必要要求是:类型T的析构函数不能抛出异常。
为了使用smart_ptr库,需要以下头文件:
#include <boost/smart_ptr.hpp>
using namespace boost;
二、各类智能指针介绍
1、scoped_ptr类
scoped_ptr是一个类似auto_ptr的智能指针,但是scoped_ptr的所有权更加严格,不能转让(不能拷贝或赋值)。除了 * 和 -> 外没有定义其他操作符。
用法举例:
<span style="white-space:pre"> </span>//构造scoped_ptr
scoped_ptr<string> sp( new string("text") );
//取字符串内容
cout<< *sp <<endl;
//取字符串的长度
cout<< sp->size() <<endl;
(1)、与auto_ptr的区别
scoped_ptr与auto_ptr共有的“缺陷”是:不能作为容器的元素;scoped_ptr的原因是不支持拷贝和赋值,不符合容器对元素类型的要求;auto_ptr的原因是:语义转移(赋值给其他指针的同时,原auto_ptr不再拥有指针);
scoped_ptr与auto_ptr的根本区别是:指针的所有权。auto_ptr特意被设计为指针的所有权是可转移的,可以在函数之间传递,同一时刻只能有一个auto_ptr管理指针。scoped_ptr把拷贝构造函数和赋值函数声明为私有的,拒绝了指针所有权的转移(除了scoped_ptr自己,其他任何人都无权访问被管理的指针,从而保证了指针的绝对安全)。
(2)、与unique_ptr的区别
std::unique_ptr是c++11标准中定义的新的智能指针,用来取代c++98中的std::auto_ptr。
std::unique_ptr比scoped_ptr的功能更强大,unique_ptr不仅能代理new创建单个对象,也可以代理new[]创建数组对象,也就是说unique_ptr结合了scoped_ptr和scoped_array两者的能力,而且与scoped_ptr相比,unique_ptr有更多的功能:可以像原始指针一样比较、可以像shared_ptr一样定制删除器,也可以安全地放入标准容器,如果你的编译器支持c++11,要毫不犹豫地使用unique_ptr来代替scoped_ptr;
unique_ptr与scoped_ptr都是在作用域内管理指针,都不允许拷贝构造和拷贝赋值。此外unique_ptr使用std::move( another_ptr),解决auto_ptr的语义转移功能。
2、scoped_array类
scoped_array,包装了new[] 操作符在堆上分配的动态数组,为动态数组提供了一个代理,保证可以正确地释放内存。
scoped_array相当于c++11标准中unique_ptr的管理数组对象功能。
scoped_array的主要特点:
*构造函数接受的指针p必须是new[]的结果,不能是new表达式的结果;
*没有*、->操作符重载,因为scoped_array持有的不是一个普通的指针;
*析构函数使用delete[]释放资源,而不是delete;
*提供operator[]操作符重载,可以像普通数组一样用下标访问元素;
*没有begin()、end()等类似容器的迭代器操作函数。
*与scoped_ptr类似,不能拷贝、赋值。
注意:
*因为不提供指针运算,所以不能用“数组首地址+N”的方式访问数组元素;
*scoped_array不提供数组索引的范围检查,如果使用了超过动态数组大小的索引或者负数索引将引发未定义的行为。
例子:
<span style="white-space:pre"> </span>int *arr = new int[100];
scoped_array<int> sa( arr );//一个整数的动态数组
fill_n(&sa[0], 100, 5);//可以使用标准库算法赋值数据
sa[10] = sa[20] + sa[30];//像普通数组一样使用
使用建议:
在需要动态数组时应该优先考虑std::vector,它比scoped_array提供了更多的灵活性。除非对性能有非常苛刻要求,或者编译器不支持标准库(某些嵌入式系统),否则不推荐使用scoped_array。
3、shared_ptr类
shared_ptr是boost.smart_ptr库中最有价值、最重要的组成部分,也是最有用的。
shared_ptr与scoped_ptr一样包装了new操作符在堆上分配的动态对象,但它是引用计数型的智能指针,可以被自由地拷贝和赋值,在任意的地方共享它。也可以安全地放入标准容器中,是在STL容器中存储指针的最标准解法。
(1)、操作函数
shared_ptr有多种形式的构造函数,应用于各种可能的情形:
* 无参的shared_ptr(): 创建一个持有空指针的shared_ptr;
* shared_ptr( Y *p): 获得指向类型T的指针p的管理权,同时引用计数置为1,要求Y类型必须能够转换为T类型;
* shared_ptr( shared_ptr const &r )从另一个shared_ptr获得指针的管理权,同时引用计数加1,结果是两个shared_ptr共享一个指针的管理权。
* shared_ptr(std::auto_ptr<Y> & r):从一个auto_ptr获得指针的管理权,引用计数置为1,同时auto_ptr自动失去管理权;
* operator = 赋值操作符可以从另外一个shared_ptr或auto_ptr获得指针的管理权;
* shared_ptr( Y *p, D d)行为类似shared_ptr(Y *p),参数d指定了析构时的定制删除器,而不是简单的delete:
reset()将引用计数减1,停止对指针的共享;
reset( Y *p)等带参数的reset:是原指针引用计数减1的同时改为管理另一个指针。
unique():返回shared_ptr是否是指针的唯一所有者;
use_count()返回当前指针的引用计数,要小心使用,它不提供高效率的操作,应该仅仅用于测试或者调试。
支持比较操作:相等或者不等、operator <,比较基于内部保存的指针。
支持流输出操作符operator<<,输出内部指针值,方便调试。
重要内容:编写基于虚函数的多态代码时的指针类型转换:
shared_ptr提供 static_pointer_cast<T>()、const_pointer_cast<T>()、dynamic_pointer_cast<T>()来实现指针转型,返回的是转型后的shared_ptr, 与标准库的转型操作符:static_cast<T>、const_cast<T>、dynamic_cast<T>类似。
例如:
shared_ptr<std::exception> sp1( new bad_exception(" error") );
shared_ptr<std::exception> sp2 = dynamic_pointer_cast<bad_exception>(sp1);
shared_ptr<std::expection> sp3 = static_pointer_cast<std::exception>(sp2);
简单使用:
<span style="white-space:pre"> </span>//一般构造函数
boost::shared_ptr< int > sp1( new int(10) );
//判断sp1是否是指针的唯一所有者
assert( sp1.unique() );
//拷贝构造函数
boost::shared_ptr< int > sp2 = sp1;
//判断两个shared_ptr是否相等(指向同一个对象),引用计数是否为2
assert( sp1 == sp2 && sp1.use_count() == 2 );
//使用解引用操作符修改被指对象
*sp2 = 100;
assert(*sp1 == 100);//另一个shared_ptr也同时被修改
//停止shared_ptr sp1的使用
sp1.reset();
assert( !sp1);//sp1不再持有任何指针(空指针)
(2)、使用工厂函数
使用make_shared(),可以消除显式的new 调用。
例:shared_ptr<string> sp = make_shared<string> ("make_shared");
shared_ptr< vector<int> > spv = make_shared<vector<int> >(10, 2);
(3)、应用于标准容器
用法一:将容器作为shared_ptr管理的对象,如shared_ptr< list<t> >。
用法二:将shared_ptr作为容器的元素,如 vector< shared_ptr<T> >。
注:存储shared_ptr的容器与存储原始指针的容器功能几乎一样,但shared_ptr为程序员做了指针的管理工作,可以任意使用shared_ptr而不用担心资源泄露。
typedef vector< boost::shared_ptr<int> > VS;
VS v1(10);
int i=0;
for( VS::iterator pos = v1.begin(); pos !=v1.end(); ++pos )
{
(*pos) = boost::make_shared< int >(++i);
cout<<*(*pos) <<",";
}
cout<<endl;
boost::shared_ptr<int> p = v1[9];
*p = 100;
cout<< *v1[9] <<endl;
(4)、应用于桥接模式
桥接模式(bridge)是一种结构型设计模式,它把类的具体实现细节对用户隐藏起来,以达到类之间的最小耦合关系。在具体编程实践中桥接模式也被称为pimpl或者handle/body惯用法,参看http://blog.csdn.net/xiaoxiaoyusheng2012/article/details/18526237,桥接模式可以将头文件的依赖关系降到最小,减少编译时间,而且可以不使用虚函数实现多态。
scoped_ptr和shared_ptr都可以用来实现桥接模式,但是shared_ptr通常更合适,因为它支持拷贝和赋值,这在很多情况下都是有用的,比如可以配合容器工作。
(5)、应用于工厂模式
工厂模式是一种创建型设计模式,这个模式包装了new操作符的使用,使对象的创建工作集中在工厂类或者工厂函数中,从而更容易适应变化,然而,通常工厂类或工厂函数需要在堆上使用new动态分配一个对象,然后返回该对象的指针。这种做法很不安全,因为用户很容易忘记对指针调用delete,存在资源泄露的隐患。
使用shared_ptr可以解决这个问题,只需要修改工厂方法的接口,不再返回一个原始指针,而是返回一个被shared_ptr包装的智能指针,这样可以很好地保护系统资源,而且还会更好地控制对接口的使用。
#ifndef SHAREDPTR_ABSTRUCT_H_
#define SHAREDPTR_ABSTRUCT_H_
#include <iostream>
using namespace std;
class abstruct
{
public:
virtual void f()=0;
virtual void g()=0;
protected:
virtual ~abstruct(){} //abstruct的析构函数被定义为保护的,意味着除了它自己和它的子类,其他任何对象都无权调用delete来删除它。
};
class impl:public abstruct
{
public:
virtual void f()
{
cout << "class impl f" << endl;
}
virtual void g()
{
cout << "class impl g" <<endl;
}
};
boost::shared_ptr< abstruct > create()
{
return boost::shared_ptr< abstruct >( new impl);
}
#endif //SHAREDPTR_ABSTRUCT_H_
#include "stdafx.h"
#include <boost/smart_ptr/shared_ptr.hpp>
#include "abstract.h"
using namespace boost;
int _tmain(int argc, _TCHAR* argv[])
{
boost::shared_ptr< abstruct > p = create();//工厂函数创建对象
p->f();
p->g();
while(1);
return 0;
}
结果:
(6)、定制删除器
shared_ptr( Y* p, D d);//第一个参数是被管理的指针,第二个参数是删除器d,告诉shared_ptr在析构时不是使用delete来操作指针p,而要用d来操作,即把delete p换成 d(p);
删除器d可以是函数对象,也可以是一个函数指针,只要它能够像函数那样被调用,使得d(p)成立。对删除器的要求是它必须可拷贝,行为必须像delete那样,不能抛出异常。
为了配合删除器的工作,shared_ptr提供了一个自由函数get_deleter( shared_ptr<T> const &p),它能够返回删除器的指针。
删除器的使用例子:
class socket_t
{
public:
socket_t()
{
}
void run()
{
cout<< "socket_t::run()" <<endl;
}
virtual ~socket_t()
{
}
};
socket_t* open_socket()
{
cout<< "open_socket" <<endl;
return new socket_t();
}
void close_socket( socket_t* s)
{
cout<< "close_socket" <<endl;
//...//其他操作,释放资源
delete s;
}
socket_t *s = open_socket();
boost::shared_ptr< socket_t> p1(s, close_socket);
(7)、其他
C++11标准中定义了std::shared_ptr,多了>、<=等操作符的重载,功能与boost::shared_ptr基本相同,基本可以等价互换。
shared_ptr< void >能够存储void*型的指针,而void*指针可以指向任意类型,因此shared_ptr<void*> 就像一个泛型的指针容器,拥有容纳任意类型的能力,注意可以使用static_pointer_cast<T>等转型函数重新转为原来的指针,但这涉及到运行时动态类型转换,它会使代码不够安全,建议最好不要这样使用。
删除器的高级用法:
基于shared_ptr<void> 和定制删除器,shared_ptr可以有惊人的用法。例如可以实现在退出作用域时执行任意函数。
例如:
void any_func( void *p)
{
cout<< "some operator " <<endl;
while(1);
}
int _tmain(int argc, _TCHAR* argv[])
{
boost::shared_ptr<void> p2( (void*)0, any_func);
return 0;
}
结果:
4、shared_array
shared_array类似shared_ptr,它包装了new[]操作符在堆上分配的动态数组,同样使用引用计数机制为动态数组提供了一个代理,可以在程序的生命周期里长期存在,直到没有任何引用后才释放内存。
shared_array的接口与功能几乎是与shared-ptr是相同的,主要区别是:
*构造函数接受的指针p必须是new[] 的结果,而不是new表达式的结果;
*提供operator[]操作符重载,可以像普通数组一样用下标访问元素;
*没有*.->操作符重载,因为shared_array持有的不是一个普通指针;
*析构函数使用delete[]释放资源,而不是delete。
用法:
int *p = new int[100]; //一个动态数组
boost::shared_array< int > sa(p); //shared_array代理动态数组
boost::shared_array< int > sa2 = sa; //共享数组,引用计数增加
sa[10] = 10; //可以使用operator[]访问元素
assert( sa2[10] == 10 );
//离开作用域,自动删除动态数组
注意:
在使用shared_array重载的operator[]时要小心,shared_array不提供数组索引的范围检查,如果使用超过动态数组大小的索引或者是负数索引将引发可怕的未定义行为。
shared_array能力有限, 多数情况下它可以用 shared_ptr< std::vector> 或者 std::vector<shared_ptr>来代替,这两个方案具有更好地安全性和更多的灵活性,而所付出的代价几乎可以忽略不计。
5、weak_ptr
weak_ptr是为了配合shared_ptr而引入的一种智能指针,它更像是shared_ptr的一个助手而不是智能指针,因为它不具有普通指针的行为,没有重载operator* 和->。它的最大作用在于协助shared_ptr工作,像旁观者那样观测资源的使用情况。
weak_ptr被设计与shared_ptr共同工作,可以从一个shared_ptr或者另一个weak_ptr对象构造,获取资源的观测权。但weak_ptr没有共享资源,它的构造不会引起指针引用计数的增加。同样在weak_ptr析构时也不会导致引用计数减少,它只是一个观察者。
用法:
boost::shared_ptr< int > sp( new int(10) );
assert( sp.use_count() == 1);
boost::weak_ptr< int > wp( sp );//从shared_ptr构造一个weak_ptr
boost::weak_ptr< int > wp1( sp );
assert( wp.use_count() == 1 );//weak_ptr不影响引用计数
if( !wp.expired()) //判断weak_ptr观察的对象是否失效,即sp.use_count() == 0 是否成立
{
boost::shared_ptr<int> sp2 = wp.lock();//获得一个shared_ptr,指针引用计数加一
*sp2 = 100;
assert( wp.use_count() == 2);
//sp2退出作用域, 指针引用计数减一
}
assert( wp.use_count() == 1);
sp.reset();//使sp指针停止对指针的共享,指针引用计数减一
assert( wp.expired() );//即wp.use_count() == 0
assert( !wp.lock() );//weak_ptr将获得一个空指针
wp.reset();//重置指针
wp.swap( wp1 );//交换指针
其他用法:
1*、获得this的shared_ptr
weak_ptr的一个重要用途是获得this指针的shared_ptr,使对象自己能够生产shared_ptr管理自己:对象使用weak_ptr观察this指针,这并不影响引用计数,在需要的时候就调用lock()函数,返回一个符合要求的shared_ptr供外界使用。
这个解决方案被实现为一个惯用法,在头文件<boost/enable_shared_from_this.hpp>定义了一个助手类enable_shared_from_this<T>,它的声明摘要如下:
template< class T >
class enable_shared_from_this
{
public:
shared_ptr<T> shared_from_this();
}
使用的时候只需要让想被shared_ptr管理的类从它继承即可,成员函数shared_from_this()会返回this的shared_ptr。
例子:
class MyselfShared :public boost::enable_shared_from_this< MyselfShared >
{
public:
MyselfShared( int n):x(n){}
MyselfShared():x(0){}
int x;
void print()
{
std::cout<< "self_shared:" <<std::endl;
}
};
boost::shared_ptr< MyselfShared > spp = boost::make_shared< MyselfShared >(100);
spp->print();
boost::shared_ptr< MyselfShared > p3 = spp->shared_from_this() ;
p3->x = 1000;
p3->print();
结果:
注意:千万不能从一个普通对象(非shared_ptr)使用shared_from_this()获取shared_ptr,这样虽然语法上正确,编译也没问题,但是运行时会导致shared_ptr析构时企图删除一个栈上分配的对象,发生未定义行为。
例如:MyselfShared ss;
shared_ptr< self_shared> p = ss.shared_from_this(); //错误
2*、打破循环引用
有时候代码中可能会出现“循环引用”的情形,shared_ptr的引用计数机制就会失效,导致不能正确释放资源。
出现循环引用的场景:
class node
{
public:
~node()
{
std::cout << "deleted " << std::endl;
}
int x;
typedef boost::shared_ptr< node > ptr_type;
ptr_type next;
};
boost::shared_ptr< node > pp1 = boost::make_shared< node >( );
boost::shared_ptr< node > pp2 = boost::make_shared< node >( );
pp1->next = pp2;
pp2->next = pp1;
assert( pp1.use_count() == 2 );
assert( pp2.use_count() == 2 );
结果:由于出现循环引用,在退出作用域时,上述共享指针的引用计数始终为2,不会减至0,导致内存泄露;
解决循环引用的方法:
在可能出现循环引用的地方,使用weak_ptr将强引用改变为弱引用,从而打破循环引用,而在真正需要shared_ptr的时候调用weak_ptr的lock()函数
如下:上述循环引用场景的修改:将 class node 中的强引用改为弱引用
class node
{
public:
~node()
{
std::cout << "deleted " << std::endl;
}
int x;
//typedef boost::shared_ptr< node > ptr_type;
typedef boost::weak_ptr< node > ptr_type;
ptr_type next;
};
boost::shared_ptr< node > pp1 = boost::make_shared< node >( );
boost::shared_ptr< node > pp2 = boost::make_shared< node >( );
pp1->next = pp2;
pp2->next = pp1;
assert( pp1.use_count() == 1 );//每个shared_ptr的引用计数都是1
assert( pp2.use_count() == 1 );
if( !pp1->next.expired() )
{
boost::shared_ptr< node > pp3 = pp1->next.lock();//调用lock()获得强引用
}
结果:正确析构,“deleted”输出两次。
6、intrusive_ptr
intrusive_ptr是一个侵入式的引用计数型指针,它可以用于以下两种情形:
* 对内存占用的要求非常严格,要求必须与原始指针一样;
* 现存代码已经有了引用计数机制管理的对象;
不推荐使用intrusive_ptr,因为shared_ptr已经非常强大且灵活,工作的足够好,可以满足绝大部分的需要。