auto

    技术2022-05-13  19

    shared_ptr

    shared_ptr是Boost库所提供的一个智能指针的实现,正如其名字所蕴意的一样:

    An important goal of shared_ptr is to provide a standard shared-ownership pointer.

    shared_ptr的一个重要目的就是为了提供一个标准的共享所有权的智能指针。

    —— Boost库文档

    没错,shared_ptr就是为了解决auto_ptr在对象所有权上的局限性(auto_ptr是独占的),在使用引用计数的机制上提供了可以共享所有权的智能指针,当然这不会没有任何额外的代价……

    首先一个shared_ptr对象除了包括一个所拥有对象的指针(px)外,还必须包括一个引用计数代理对象(shared_count)的指针(pn)。而这个引用计数代理对象包括一个真正的多态的引用计数对象(sp_counted_base)的指针(_pi),真正的引用计数对象在使用VC编译器的情况下包括一个虚表,一个虚表指针,和两个计数器。

    下图中result是一个shared_ptr对象,我们可以清楚看到它展开后所包含的数据:

    假设我们有多个(5个以上)shared_ptr共享一个动态对象,那么每个shared_ptr的开销比起只使用原生指针的开销大概在3,4 倍左右(这还是理想状况,忽略了动态分配带来的俑余开销)。如果只有一个shared_ptr独占动态对象,空间上开销更是高度十数倍!而 auto_ptr的开销只是使用原生指针的两倍。

    时间上的开销主要在初始化和拷贝操作上,*和->操作符重载的开销跟auto_ptr是一样的。

    当然开销并不是我们不使用shared_ptr的理由,永远不要进行不成熟的优化,直到性能分析器告诉你这一点,这是Hurb提出的明智的建议。以上的说明只是为了让你了解强大的功能背后总是伴随着更多的开销,shared_ptr应该被使用,但是也不要过于滥用,特别是在一些 auto_ptr更擅长的地方。

    下面是shared_ptr的类型定义:

    template class shared_ptr ...{ public:       typedef T element_type;       shared_ptr(); // never throws       template explicit shared_ptr(Y * p);       template shared_ptr(Y * p, D d); ~shared_ptr(); // never throws       shared_ptr(shared_ptr const & r); // never throws       template shared_ptr(shared_ptr const & r); // never throws       template explicit shared_ptr(weak_ptr const & r);       template explicit shared_ptr(std::auto_ptr & r);       shared_ptr & operator=(shared_ptr const & r); // never throws        template shared_ptr & operator=(shared_ptr const & r); // never throws       template shared_ptr & operator=(std::auto_ptr & r); void reset(); // never throws       template void reset(Y * p);       template void reset(Y * p, D d);       T & operator*() const; // never throws       T * operator->() const; // never throws       T * get() const; // never throws bool unique() const; // never throws long use_count() const; // never throws operator unspecified-bool-type() const; // never throws void swap(shared_ptr & b); // never throws };

    大多数成员函数都跟auto_ptr类似,但是没有了release(请看注释),reset用来放弃所拥有对象的所有权或拥有对象的变更,会引起原有对象的引用计数的减少。

    Note: Boost文档里面的QA说明了为什么不提供release函数 Q. Why doesn't shared_ptr provide a release() function? A. shared_ptr cannot give away ownership unless it's unique() because the other copy will still destroy the object. Consider: shared_ptr a(new int); shared_ptr b(a); // a.use_count() == b.use_count() == 2 int * p = a.release(); // Who owns p now? b will still call delete on it in its destructor. Furthermore, the pointer returned by release() would be difficult to deallocate reliably, as the source shared_ptr could have been created with a custom deleter.

    use_count返回引用计数的个数,unique拥于确认是否是独占所有权(use_count为1),swap用于交换两个 shared_ptr对象(即交换所拥有的对象),有一个bool类型转换操作符使得shared_ptr可用于需要的bool类型的语境下,比如我们通常用if(pointer)来判断某个指针是否为空。

    Boost库里面有很多shared_ptr的使用例程,文档里面也列举了许许多多shared_ptr的用途,其中最有用也最常用的莫过于传递动态分配对象,有了引用计数机制,我们现在可以安全地将动态分配的对象包裹在shared_ptr里面跨越模块,跨越线程的边界传递。 shared_ptr为我们自动管理对象的生命周期,嗯,C++也可以体会到Java里面使用引用的美妙之处了。

    另外,还记得Effective C++里面(或者其它的C++书籍),Scott Meyer告诉你的:在一个由多个模块组成的系统里面,一个模块不用试图自己去释放另外一个模块分配的资源,而应该遵循谁分配谁释放的原则。正确的原则但是有时难免有时让人忽略(过于繁琐),将资源包装在shared_ptr里面传递,而shared_ptr保证了在资源不再被拥有的时候,产生资源的模块的delete语句会被调用。

    shared_ptr是可以拷贝和赋值的,拷贝行为也是等价的,并且可以被比较,这意味这它可被放入标准库的一般容器(vector,list)和关联容器中(map)。

    shared_ptr可以用来容纳多态对象,比如所下面的例子:

    class Base ...{ } class Derived : public Base ...{ } shared_ptr sp_base(new Derived);

    甚至shared_ptr也具备多态的行为:

    Derived* pd = new Derived; shared_ptr sp_derived(pd); shared_ptr sp_base2(sp_derived);

    上面的语句是合法的,shared_ptr会完成所需的类型转换,当shared_ptr的模版参数Base的确是Derived的基类的时候。

    最后是一个小小的提醒,无论是使用auto_ptr还是shared_ptr,都永远不要写这样的代码:

    A* pa = new A; xxx_ptr ptr_a_1(pa); xxx_ptr ptr_a_2(pa);

    很明显,在ptr_a_1和ptr_a_2生命周期结束的时候都会去删除pa,pa被删除了两次,这肯定会引起你程序的崩溃,当然,这个误用的例子比较明显,但是在某种情况下,可能会一不小心就写出如下的代码(嗯,我承认我的确做过这样的事情):

    void DoSomething(xxx_ptr ) ...{ //do something } class A ...{          doSomething() ...{                  xxx_ptr ptr_a(this);                  DoSomething(ptr_a);          } }; int main() ...{          A a;          a.doSomething(); //continue do something with a, but it was already destory }

    在函数a.doSomething()里面发生了什么事情,它为了调用DoSomething所以不得不把自己包装成一个xxx_ptr,但是忘记在函数结束的时候,xxx ptr_a被销毁的同时也销毁了自己,程序或者立刻崩溃或者在下面的某个时间点上崩溃!

    所以你在使用智能指针做为函数参数的时候请小心这样的误用,有时候使用智能指针作为函数参数不一定是一个好注意。比如请遵循下面的建议,请不要在属于类型A的接口的一部分的非成员函数或者跟A有紧密联系的辅助函数里面使用xxx_ptr 作为函数的参数类型。

    原文出处(点击此处)


    最新回复(0)