智能指针的原理是基于RAII:申请资源即初始化。一个动态分配的对象由智能指针管理,在任何情况下动态分配的内存都能得到正确释放,从而将开发人员从这项任务中解放了出来。
[b]作用域指针:[/b]
boost::scoped_ptr,一个作用域指针独占一个动态分配的对象,不能将一个作用域指针所包含的对象传递给另一个作用域指针。
一经初始化,智能指针 boost::scoped_ptr 所包含的对象,可以通过类似于普通指针的接口来访问。 这是因为重载了相关的操作符 operator*(),operator->() 和 operator bool() 。 此外,还有 get() 和 reset() 方法。 前者返回所含对象的地址,后者用一个新的对象来重新初始化智能指针。 在这种情况下,新创建的对象赋值之前会先自动释放所包含的对象。
boost::scoped_ptr 的析构函数中使用 delete 操作符来释放所包含的对象。 这对 boost::scoped_ptr 所包含的类型加上了一条重要的限制: boost::scoped_ptr 不能用动态分配的数组来做初始化,因为这需要调用 delete[] 来释放。
[b]作用域数组:[/b]
boost::scoped_array,作用域数组的使用方式与作用域指针相似。 关键不同在于,作用域数组的析构函数使用 delete[] 操作符来释放所包含的对象。 因为该操作符只能用于数组对象,所以作用域数组必须通过动态分配的数组来初始化。
[b]共享指针:[/b]
boost::shared_ptr,智能指针 boost::shared_ptr 基本上类似于 boost::scoped_ptr。 关键不同之处在于 boost::shared_ptr 不一定要独占一个对象。 它可以和其他 boost::shared_ptr 类型的智能指针共享所有权。 在这种情况下,当引用对象的最后一个智能指针销毁后,对象才会被释放。
shared_ptr相对于标准库中auto_ptr来说,有一项重要的改进,可以再容器中存放shared_ptr对象。但是你不能在标准容器中存储 std::auto_ptr,因为它们在拷贝的时候传递了所有权。
#include
#include
#include
using namespace std;
int main()
{
typedef vector< boost::shared_ptr > ShareV;
ShareV v1;
v1.push_back(boost::shared_ptr(new int(1)));
v1.push_back(boost::shared_ptr(new int(2)));
for (ShareV::const_iterator it = v1.begin(); it != v1.end(); ++it) {
cout << **it << endl;
}
return 0;
}
[b]共享数组:[/b]
boost::shared_array,共享数组的行为类似于共享指针。 关键不同在于共享数组在析构时,默认使用 delete[] 操作符来释放所含的对象。 因为这个操作符只能用于数组对象,共享数组必须通过动态分配的数组的地址来初始化。
[b]弱指针:[/b]
boost::weak_ptr,弱指针只有在配合共享指针一起使用时才有意义。一个强引用当被引用的对象活着的话,这个引用也存在(就是说,当至少有一个强引用,那么这个对象就不能被释放)。boost::share_ptr就是强引用。
相对而言,弱引用当引用的对象活着的时候不一定存在。仅仅是当它存在的时候的一个引用。弱引用并不修改该对象的引用计数,这意味这弱引用它并不对对象的内存进行管理,在功能上类似于普通指针,然而一个比较大的区别是,弱引用能检测到所管理的对象是否已经被释放,从而避免访问非法内存。
weak_ptr的lock函数返回一个引向weak_ptr所观察的资源的 shared_ptr ,如果可以的话。如果没有这样指针(即 weak_ptr 引向的是空指针),shared_ptr 也将引向空指针。否则,shared_ptr所引向的资源的引用计数将正常地递增。
#include
#include
#include
using namespace std;
void * reset(void *arg)
{
boost::shared_ptr *sh = static_cast *>(arg);
sleep(1);
sh->reset(); // 重置
}
void * print(void *arg)
{
boost::weak_ptr *w = static_cast *>(arg);
boost::shared_ptr sh = w->lock(); // 如果已经被重置了,则返回空;如果没有被重置,则返回一个shared_ptr并且引用计数加1,这样永远都不会出错了
if (sh)
cout << *sh << endl;
}
int main()
{
boost::shared_ptr i(new int(4));
boost::weak_ptr w(i);
int err = 0;
pthread_t tid1, tid2;
err = pthread_create(&tid1, NULL, reset, &i);
if(err != 0) {
cerr << "can't create pthread 1" << endl;
exit(1);
}
err = pthread_create(&tid2, NULL, print, &w);
if( err != 0 ) {
cerr << "can't create pthread 2" << endl;
exit(1);
}
err = pthread_join(tid1, NULL);
if (err != 0) {
cerr << "can't join thread 1" << endl;
exit(1);
}
err = pthread_join(tid2, NULL);
if( err != 0 ) {
cerr << "can't join thread 2" << endl;
exit(1);
}
return 0;
}
那么weak_ptr有什么用呢?一个最大的用处就是管理循环引用的对象。下面我们看一个例子:
#include
#include
#include
#include
class parent;
class children;
typedef boost::shared_ptr parent_ptr;
typedef boost::shared_ptr children_ptr;
class parent
{
public:
~parent() { std::cout <<"destroying parent\n"; }
public:
children_ptr children;
};
class children
{
public:
~children() { std::cout <<"destroying children\n"; }
public:
parent_ptr parent;
};
void test()
{
parent_ptr father(new parent());
children_ptr son(new children);
father->children = son;
son->parent = father;
}
int main()
{
std::cout<<"begin test...\n";
test();
std::cout<<"end test.\n";
return 0;
}
注意看test函数,即使退出了test函数后,由于parent和children对象互相引用,它们的引用计数都是1,不能自动释放,并且此时这两个对象再无法访问到。这就引起了c++中那臭名昭著的内存泄漏。我们看一下内存布局图:
[img]http://dl.iteye.com/upload/attachment/360717/0bbffb53-885e-31c3-820b-f360f11121d4.png[/img]
当test结束的时候,局部变量father和son会析构,father析构的时候,由于他持有对象的引用计数为2,所以仅仅将引用计数减1,son也一样,test函数结束后,内存布局如下图:
[img]http://dl.iteye.com/upload/attachment/360719/dd65ed11-a719-3950-976e-38cefab6b129.png[/img]
我们可以通过boost::weak_ptr来打破循环引用
由于弱引用不更改引用计数,类似普通指针,只要把循环引用的一方使用弱引用,即可解除循环引用。对于上面的那个例子来说,只要把children的定义改为如下方式,即可解除循环引用:
class children
{
public:
~children() { std::cout <<"destroying children\n"; }
public:
boost::weak_ptr parent;
};
最后值得一提的是,虽然通过弱引用指针可以有效的解除循环引用,但这种方式必须在程序员能预见会出现循环引用的情况下才能使用,也可以是说这个仅仅是一种编译期的解决方案,如果程序在运行过程中出现了循环引用,还是会造成内存泄漏的。因此,不要认为只要使用了智能指针便能杜绝内存泄漏。毕竟,对于C++来说,由于没有垃圾回收机制,内存泄漏对每一个程序员来说都是一个非常头痛的问题。
[b]介入式指针:[/b]
大体上,介入式指针的工作方式和共享指针完全一样。 boost::shared_ptr 在内部记录着引用到某个对象的共享指针的数量,可是对介入式指针来说,程序员就得自己来做记录。 对于框架对象来说这就特别有用,因为它们记录着自身被引用的次数。
[b]指针容器:[/b]
在你见过 Boost C++ 库的各种智能指针之后,应该能够编写安全的代码,来使用动态分配的对象和数组。多数时候,这些对象要存储在容器里——如上所述——使用 boost::shared_ptr 和 boost::shared_array 这就相当简单了。
然而因为某些原因,实际情况中并不这么用。 第一,反复声明 boost::shared_ptr 需要更多的输入。 其次,将 boost::shared_ptr 拷进,拷出,或者在容器内部做拷贝,需要频繁的增加或者减少内部引用计数,这肯定效率不高。 由于这些原因,Boost C++ 库提供了 指针容器 专门用来管理动态分配的对象。
boost::ptr_vector 独占它所包含的对象,因而容器之外的共享指针不能共享所有权,这跟 std::vector
> 相反。