shared_ptr应用于标准容器,一种是容器作为它的管理对象,如shared_ptr<list<T>>,使容器可以安全的共享,容器此时就类似于一个类型对象;一种是将shared_ptr作为容器的元素,如vector<shared_ptr<T>>,因为shared_ptr是支持拷贝语义和比较操作的,符合标准容器对元素的要求,因此可以实现在容器中安全容纳元素的指针而不是拷贝。
值得注意的是,标准容器不能容纳auto_ptr(有转移语义),这是C++标准特别规定的。标准容器也不能容纳scoped_ptr,因为它不能拷贝和赋值。标准容器可以容纳指针,但由于容器无法自动管理指针元素,须额外用大量代码去做管理保证能够安全删除,制造了不少麻烦。
不过存储shared_ptr就不一样了,它的效果与指针基本相同,但本身却做好了指针管理工作,因此少了对资源泄漏的担忧。下面代码示范具体的应用操作:
int main()
{
typedef vector<shared_ptr<int>>vs;
vs v(10);
int i = 0;
for (vs::iterator pos = v.begin(); pos != v.end(); ++pos)
{
(*pos) = make_shared<int>(++i);
cout << *(*pos) <<", ";
}
cout << endl;
shared_ptr<int> p = v[9];
*p = 100;
cout << *v[9] << endl;
}
这里面有个需要注意的点是,对容器迭代器解引用得到shared_ptr,再解引用得到的才是真正的值。
桥接模式是C++设计模式的一种,它把类的具体实现细节对用户隐藏起来,以达到类之间的最小耦合关系。具体实践中我们熟悉的名字可能是pimpl或者handle惯用法,它能够将头文件依赖性降到最低从而减少编译时间,而且还能避免使用虚函实现多态。这里不会详尽讲解这个设计模式的全部内容,以后会有文章来单独介绍,在此主要说明shared_ptr如何用于pimpl:
class sample
{
private:
class impl;
shared_ptr<impl>p;
public:
sample();
void print();
};
在sample的cpp中完整定义impl类和其他功能:
class sample::impl //内部类的实现
{
public:
void print()
{ cout << "impl print" << endl;}
};
sample::sample():p(new impl){}
void sample::print()
{p->print();}
具体的使用:
smaple s;
s.print();
桥接模式非常的好用,能够在使用者一无所知的情况下对任意实现做需要的修改,减小了源文件之间的依赖性,带来了良好的灵活性。shared_ptr是这项功能一个很不错的实现工具,它解决了指针的共享和引用计数问题。
讲讲shared_ptr(Y * p, D d)这个家伙。它的第一个参数自然是指针,而第二个删除器参数d则告诉shared_ptr在析构时不是使用delete来操作指针p,而是用d,即d(p).
d既可以是一个函数对象也可以是一个函数指针,具有函数调用功能即可。但也是有要求的,它必须是可拷贝的,行为像delete那样,不允许抛出异常。
为了匹配删除器功能,shared_ptr提供了get_deleter(shared_ptr<T>const & p)函数,可返回删除器指针。典型的使用我们可以看看下列对传统的使用struct FILE的c文件操作:
shared_ptr<FILE> fp(fopen("./1.txt", "r"), fclose);
当离开作用域时,shared_ptr会自动调用fclose()函数关闭文件。
删除器可以使得我们能够自定义、扩展shared_ptr的行为,让它不仅能够管理内存资源,还成为一个“万能”的资源管理工具。
shared_ptr<void>能够存储void*型指针,它可以指向任意类型,因此它就好比一个泛型指针容器,但同时可能会丧失原来的类型信息,虽然我们可以用static_pointer_cast<T>等转型函数恢复指针,但安全性差多了,一般不推荐。
前面提到的删除器和shared_ptr<void>结合有比较高级的用法。存储一个空指针,可以实现退出作用域时调用任意函数,理论上可执行任意我们想做的工作:
void any_func(void * p)
{ ......;}
int main()
{
shared_ptr<void>p((void*)0, any_func);
}
shared_ptr的功能已经远远超过了智能指针的范围,除了以上的用法外它还可以有,如包装成员函数、延时释放等,这里不做详尽介绍,可以在boost说明文档查询学习。
weak_ptr是为了配合shared_ptr而引入的一种智能指针,它更像是shared_ptr的助手而不是智能指针,它并没有重载operator*和->,不能操作资源。它没有共享资源,它的构造不会引起指针引用计数的增加。同样析构时也不会导致引用计数减少,只是一个静静的观察者。
虽然作为“弱”指针,但它可以使用一个重要的成员函数lock()从被观测的shared_ptr获得一个可用的shared_ptr对象,从而操作资源。但当expired() == true的时候,lock()函数将返回一个存储空指针的shared_ptr:
shared_ptr<int>sp(new int(10));
weak_ptr<int>wp(sp);
shared_ptr<int>sp2 = wp.lock();
weak_ptr的一个重要用途是获得this指针的shared_ptr,使对象自己能够生产shared_ptr管理自己:在需要的时候调用lock()函数,返回一个符合要求的shared_ptr供外界使用。
这个解决方案在头文件<boost/enable_shared_from_this.hpp>定义了一个助手类enable_shared_from_this<T>,使用的时候只需要让想被管理的类从它继承即可,成员函数shared_from_this()会返回this的shared_ptr。例如:
需要注意的是千万不能从一个普通对象(非shared_ptr)使用shared_from_this()获取shared_ptr,因为会导致shared_ptr析构时企图删除一个栈上分配的对象,发生未定义行为。
boost真可谓是一个宝库,尤其在思想上能够带给我们太多的启发,篇幅有限只做了较为重要的智能指针(像scoped_ptr并没提到,因为它仅在作用域可用,使用不太多)这一块介绍,其他用起来觉得不错的后续会有补充。
更多技术交流可扫二维码或搜索关注微信公众号“嵌入式软件开发者”,我们可以在那里分享经验,共同成长!