分享

Boost智能指针

 昵称2712627 2010-08-13

Boost智能指针——weak_ptr


引用计数是一种便利的内存管理机制,但它有一个很大的缺点,那就是不能管理循环引用的对象。一个简单的例子如下:

#include <string>
#include <iostream>
#include <boost/shared_ptr.hpp>
#include <boost/weak_ptr.hpp>

class parent;
class children;

typedef boost::shared_ptr<parent> parent_ptr;
typedef boost::shared_ptr<children> 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;
}

void main()
{
    std::cout<<"begin test...\n";
    test();
    std::cout<<"end test.\n";
}

运行该程序可以看到,即使退出了test函数后,由于parent和children对象互相引用,它们的引用计数都是1,不能自动释放,并且此时这两个对象再无法访问到。这就引起了c++中那臭名昭著的内存泄漏

一般来讲,解除这种循环引用有下面有三种可行的方法:

  1. 当只剩下最后一个引用的时候需要手动打破循环引用释放对象。
  2. 当parent的生存期超过children的生存期的时候,children改为使用一个普通指针指向parent。
  3. 使用弱引用的智能指针打破这种循环引用。

虽然这三种方法都可行,但方法1和方法2都需要程序员手动控制,麻烦且容易出错。这里主要介绍一下第三种方法和boost中的弱引用的智能指针boost::weak_ptr。

强引用和弱引用

一个强引用当被引用的对象活着的话,这个引用也存在(就是说,当至少有一个强引用,那么这个对象就不能被释放)。boost::share_ptr就是强引用。

相对而言,弱引用当引用的对象活着的时候不一定存在。仅仅是当它存在的时候的一个引用。弱引用并不修改该对象的引用计数,这意味这弱引用它并不对对 象的内存进行管理,在功能上类似于普通指针,然而一个比较大的区别是,弱引用能检测到所管理的对象是否已经被释放,从而避免访问非法内存。

boost::weak_ptr

boost::weak_ptr<T>是boost提供的一个弱引用的智能指针,它的声明可以简化如下:

namespace boost {

    template<typename T> class weak_ptr {
    public:
        template <typename Y>
        weak_ptr(const shared_ptr<Y>& r);

        weak_ptr(const weak_ptr& r);

        ~weak_ptr();

        T* get() const;
        bool expired() const;
        shared_ptr<T> lock() const;
    };
}

可以看到,boost::weak_ptr必须从一个boost::share_ptr或另一个boost::weak_ptr转换而来,这也说 明,进行该对象的内存管理的是那个强引用的boost::share_ptr。boost::weak_ptr只是提供了对管理对象的一个访问手段。

boost::weak_ptr除了对所管理对象的基本访问功能(通过get()函数)外,还有两个常用的功能函数:expired()用于检测所管理的对象是否已经释放;lock()用于获取所管理的对象的强引用指针。

通过boost::weak_ptr来打破循环引用

由于弱引用不更改引用计数,类似普通指针,只要把循环引用的一方使用弱引用,即可解除循环引用。对于上面的那个例子来说,只要把children的定义改为如下方式,即可解除循环引用:

class children
{
public:
    ~children() { std::cout <<"destroying children\n"; }

public:
    boost::weak_ptr<parent> parent;
};

最后值得一提的是,虽然通过弱引用指针可以有效的解除循环引用,但这种方式必须在程序员能预见会出现循环引用的情况下才能使用,也可以是说这个仅仅 是一种编译期的解决方案,如果程序在运行过程中出现了循环引用,还是会造成内存泄漏的。因此,不要认为只要使用了智能指针便能杜绝内存泄漏。毕竟,对于 C++来说,由于没有垃圾回收机制,内存泄漏对每一个程序员来说都是一个非常头痛的问题。

two weak_ptr instances are equivalent if and only if they share ownership or are both empty.

智能指针 boost(scoped_ptr,scoped_array,shared_ptr,shared_array) 和 std (auto_ptr)的比较

std::auto_ptr相信都接触使用过,对于一般的用户它绝对够用了,new一块内存自动释放
int main()
{
  std::auto_ptr<int> p(new int);

  return 0;
}
但它的不足之处在与,对象拥有者只有一个,换句话说:就是new出来的内存空间只属于一个对象
也就是说,auto_ptr会转交控制权
int main()
{
   std::auto_ptr<str::string> p(new string("123"));
   std::auto_ptr<str::string> p1(p);

   cout << p << endl; // p的值已近为空
   cout << p1 << endl; // 123
}

2、scoped_ptr、scoped_array

scoped_ptr与auto_ptr相似, scoped_ptr智能指针与std::auto_ptr不同在于,它明确禁止任何想要这样做的企图!这在你需要确保指针任何时候只有一个拥有者时的任何一种情境下都是非常重要的

scoped_ptr通过成员还是的private私有化来禁止拷贝 nocopyable

scoped_array与scoped_ptr显然是意义等价的,但是是用来处理数组的

3、shared_ptr,shared_array

boost::scoped_ptr虽然简单易用,但它不能共享所有权的特性却大大限制了其使用范围,而   boost::shared_ptr可以解决这一局限。顾名思义,boost::shared_ptr是可以共享所有权的智能指针,首先让我们通过一个例子看看它的基本用法:

#include <string>
#include <iostream>
#include <boost/shared_ptr.hpp>

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

void test()
{
    boost::shared_ptr<implementation> sp1(new implementation());
    std::cout<<"The Sample now has "<<sp1.use_count()<<" references\n";

    boost::shared_ptr<implementation> sp2 = sp1;
    std::cout<<"The Sample now has "<<sp2.use_count()<<" references\n";
   
    sp1.reset();
    std::cout<<"After Reset sp1. The Sample now has "<<sp2.use_count()<<" references\n";

    sp2.reset();
    std::cout<<"After Reset sp2.\n";
}

void main()
{
    test();
}

该程序的输出结果如下:

The Sample now has 1 references
The Sample now has 2 references
After Reset sp1. The Sample now has 1 references
destroying implementation
After Reset sp2.

可以看到,boost::shared_ptr指针sp1和sp2同时拥有了implementation对象的访问权限,且当sp1和sp2都释放对该对象的所有权时,其所管理的的对象的内存才被自动释放。在共享对象的访问权限同时,也实现了其内存的自动管理。

boost::shared_ptr的内存管理机制:

boost::shared_ptr的管理机制其实并不复杂,就是对所管理的对象进行了引用计数,当新增一个boost::shared_ptr对 该对象进行管理时,就将该对象的引用计数加一;减少一个boost::shared_ptr对该对象进行管理时,就将该对象的引用计数减一,如果该对象的 引用计数为0的时候,说明没有任何指针对其管理,才调用delete释放其所占的内存。

上面的那个例子可以的图示如下:

  1. sp1对implementation对象进行管理,其引用计数为1
  2. 增加sp2对implementation对象进行管理,其引用计数增加为2
  3. sp1释放对implementation对象进行管理,其引用计数变为1
  4. sp2释放对implementation对象进行管理,其引用计数变为0,该对象被自动删除

boost::shared_ptr的特点:

和前面介绍的boost::scoped_ptr相比,boost::shared_ptr可以共享对象的所有权,因此其使用范围基本上没有什么限 制(还是有一些需要遵循的使用规则,下文中介绍),自然也可以使用在stl的容器中。另外它还是线程安全的,这点在多线程程序中也非常重要。

boost::shared_ptr的使用规则:

boost::shared_ptr并不是绝对安全,下面几条规则能使我们更加安全的使用boost::shared_ptr:

  1. 避免对shared_ptr所管理的对象的直接内存管理操作,以免造成该对象的重释放
  2. shared_ptr并不能对循环引用的对象内存自动管理(这点是其它各种引用计数管理内存方式的通病)。
  3. 不要构造一个临时的shared_ptr作为函数的参数。
    如下列代码则可能导致内存泄漏:
    void test()
    {
        foo(boost::shared_ptr<implementation>(new    implementation()),g());
    }
    正确的用法

    void test()
    {
        boost::shared_ptr<implementation> sp    (new implementation());
        foo(sp,g());
    }

一 Boost::smart_Ptr

我们学习C++都知道智能指针,例如STL中的std::auto_ptr,但是为什么要使用智能指针,使用它能带给我们什么好处呢?
最 简单的使用智能指针可以不会因为忘记delete指针而造成内存泄露。还有如果我们开发或者使用第三方的lib中的某些函数需要返回指针,这样的返回的指 针被client使用的时候,lib就会失去对返回的指针的控制,这样delete的指针的任务一般就会交给调用方client,但是如果client 忘记调用delete或是调用的时机不正确,都有可能导致问题,在这种情况下就最好使用智能指针。还有使用智能指针可以保证异常安全,保证程序在有异常抛 出时仍然无内存泄露。

std::auto_ptr很多的时候并不能满足我们的要求,比如她不能用在STL的container中。boost的smart_ptr中提供了4种智能指针和2种智能指针数组来作为std::auto_ptr的补充。  


shared_ptr<boost/shared_ptr.hpp>:使用shared_ptr进行对象的生存期自动管理,使得分享资源所有权变得有效且安全.

scoped_ptr<boost/scoped_ptr.hpp>: 用于确保能够正确地删除动态分配的对象。scoped_ptr 有着与std::auto_ptr类似的特性,而最大的区别在于它不能转让所有权而auto_ptr可以。事实上,scoped_ptr永远不能被复制或 被赋值!scoped_ptr 拥有它所指向的资源的所有权,并永远不会放弃这个所有权。

weak_ptr<boost/weak_ptr.hpp>:weak_ptr 是 shared_ptr 的观察员。它不会干扰shared_ptr所共享的所有权。当一个被weak_ptr所观察的 shared_ptr 要释放它的资源时,它会把相关的 weak_ptr的指针设为空。使用此辅助指针一般是防止悬空指针。

intrusive_ptr<boost/intrusive_ptr.hpp>:shared_ptr的插入是版本,一般不使用,因为需要对使用此指针的类型中增加ref计数功能。但是可以保证不增加指针的大小。

scoped_array<boost/scoped_array.hpp>: scoped_array 为数组做了scoped_ptr为单个对象的指针所做的事情:它负责释放内存。
shared_array<boost/shared_array.hpp>: shared_array 用于共享数组所有权的智能指针。一般指向std::vector的shared_ptr提供了比shared_array更多的灵活性,所以一般使用 std::vector<shared_ptr>。


二 源码剖析

通过上面的分析,下面主要介绍我们最常用也最有用的shared_ptr智能指针。(智能指针的实现,其实最重要的是就是重载->和*运算符)

下面是shared_ptr的头文件:
template<class Ty> class shared_ptr {
public:
    typedef Ty element_type;

    shared_ptr();
    template<class Other>
    explicit shared_ptr(Other *ptr);
    template<class Other, class D>
    shared_ptr(Other *ptr, D dtor);
    shared_ptr(const shared_ptr& sp);
    template<class Other>
    shared_ptr(const shared_ptr<Other>& sp);
    template <class Other>
    shared_ptr(const weak_ptr<Other>& wp);
    template<class Other>
    shared_ptr(const std::auto_ptr<Other>& ap);
    ~shared_ptr();

    shared_ptr& operator=(const shared_ptr& sp);
    template<class Other>
    shared_ptr& operator=(const shared_ptr<Other>& sp);
    template<class Other>
    shared_ptr& operator=(auto_ptr<Other>& ap);

    void swap(shared_ptr& s);
    void reset();
    template<class Other>
    void reset(Other *ptr);
    template<class Other, class D>
    void reset(Other *ptr, D dtor);

    Ty *get() const;
    Ty& operator*() const;
    Ty *operator->() const;
    long use_count() const;
    bool unique() const;
    operator boolean-type() const;
};


1)构造函数,可以通过一般指针,std::auto_ptr,boost::shared_ptr,boost::weak_ptr来构造,还可以构造的时候指定如果delete指针。
2)拷贝构造函数
3)get(), 得到boost::shared_ptr所封装的指针。
4)*,得到boost::shared_ptr所封装指针的值。
5)->,用于直接调用指针的成员。
6)reset(), 用于重设boost::shared_ptr,或设为空。
7) swap(),  用于交换2个boost::shared_ptr.
8) use_count(), use_count 函数返回指针的引用计数。
9) unique(),这个函数在shared_ptr是它所保存指针的唯一拥有者时返回 true ;否则返回 false。 unique 不会抛出异常。

三 实例

1)构造,拷贝构造,赋值,get,*,->, reset, swap:
#include "boost/shared_ptr.hpp"
#include <cassert>
#include <iostream>

class A

    boost::shared_ptr<int> no_;
public:
    A(boost::shared_ptr<int> no) : no_(no) {}
    void value(int i) { *no_=i;  }
};
class B

    boost::shared_ptr<int> no_;
public: 
    B(boost::shared_ptr<int> no) : no_(no) {} 
    int value() const {  return *no_;  }
};
struct deleter
{
    void operator()(int *i)
    {
        std::cout << "destroying resource at"
            << (void*)i << '\n';
        delete i;
    }
};
struct S
{
    int member;
};

int main()

    // test for constructor
    boost::shared_ptr<int> sp;  
    boost::shared_ptr<int> sp2((int*)0);
    {
        boost::shared_ptr<int> sp3(new int(3), deleter());
    }

    std::auto_ptr<int> temp(new int(10));
    boost::shared_ptr<int> sp4(temp);

    boost::shared_ptr<int> temp2(new int(14)); 
    boost::shared_ptr<int> sp5(temp2);


    // test for using the shared_ptr(get,->,*)
    int *ip = new int(3);                  
    std::cout << (void*)ip << '\n';            
    boost::shared_ptr<int> sp6(ip);               
    std::cout << (void*)sp6.get () << '\n';      

    int *ip2 = new int(3);              
    std::cout << (void*)ip2 << '\n';        
    boost::shared_ptr<int> sp7(ip2);          
    std::cout << *sp7 << '\n';             
    std::cout << (void*)&*sp7 << '\n';       

    S *s = new S;                          
    s->member = 4;                          
    boost::shared_ptr<S> sp8(s);                   
    std::cout << sp8 -> member << '\n'; 

    // test for assign
    std::cout << std::boolalpha;
    boost::shared_ptr<int> sp9(new int(0)); 
    boost::shared_ptr<int> sp10 = sp9;
    std::cout << "sp0 == sp1:" << (sp9 == sp10) << '\n'; 
    std::cout << "sp0 != sp2:" << (sp9 != sp10) << '\n';

    // test funcion: reset and swap
    boost::shared_ptr<int> sp11 (new int(0));
    boost::shared_ptr<int> sp12 (new int(1));
    sp11. swap (sp12);
    std::cout<<*sp11<<*sp12<<std::endl;
    boost::swap(sp11, sp12);
    std::cout<<*sp11<<*sp12<<std::endl;

    boost::shared_ptr<int> sp0;
    sp0.reset();
    boost::shared_ptr<int> sp01(new int(1));
    std::cout << *sp01 <<std::endl;
    sp01.reset();
    //std::cout << *sp01 <<std::endl; //error
    sp01.reset(new int(100));
    std::cout << *sp01 <<std::endl;



    // test for query shared_ptr's state (use_count,unique)
    typedef boost::shared_ptr<int> spi;
    spi sp13 ;                      
    std::cout << "empty object: " << sp13.use_count() << '\n';
    spi sp14 ((int *)0);              
    std::cout << "null pointer: " << sp14.use_count() << '\n';
    spi sp15 (new int);              
    std::cout << "one object: " << sp15.use_count() << '\n';
    {
        spi sp16(sp15);                   
        std::cout << "two objects: " << sp15.use_count() << '\n';
        std::cout << "two objects: " << sp16.use_count() << '\n';
    }
    std::cout << "one object: " << sp15.use_count() << '\n';

    std::cout << std::boolalpha;
    spi sp17;                      
    std::cout << "empty object: " << sp17.unique() << '\n';
    spi sp18((int *)0);             
    std::cout << "null pointer: " << sp18.unique() << '\n';
    spi sp19(new int);             
    std::cout << "one object: " << sp19.unique() << '\n';
    {
        spi sp20(sp19);                    
        std::cout << "two objects: " << sp19.unique() << '\n';
        std::cout << "two objects: " << sp20.unique() << '\n';
    }
    std::cout << "one object: " << sp19.unique() << '\n';

}

2) 在STL容器中的使用:
#include "boost/shared_ptr.hpp"
#include <vector>
#include <iostream>
class A
{
public: 
    virtual void sing()
    {
        std::cout <<"A::sing" <<std::endl;
    }
protected: 
    virtual ~A()
    {std::cout<<"~A"<<std::endl;};
};
class B : public A
{
public: 
    virtual void sing()
    {  
        std::cout << "B:sing"<<std::endl;
    }
    virtual ~B()
    {std::cout<<"~B"<<std::endl;}
};
boost::shared_ptr<A> createA()
{
    boost::shared_ptr<A> p(new B());
    return p;
}
int main()

    typedef std::vector<boost::shared_ptr<A> > container_type;
    typedef container_type::iterator iterator;
    container_type container;
    for (int i=0;i<10;++i)
    {  
        container.push_back(createA());
    } 
    std::cout << "The choir is gathered: \n";
    iterator end=container.end();
    for (iterator it=container.begin();it!=end;++it)
    {    (*it)->sing(); }
}

boost中提供了几种智能指针方法:scoped_ptr shared_ptr intrusive_ptr weak_ptr,而标准库中提供的智能指针为auto_ptr.
这其中,我最喜欢,使用最多的是shared_ptr,也最让人随心所欲.
使用很简单,如下:
头文件 <boost/shared_ptr.hpp>
class A
{
  virtual void process();
}
boost::shared_ptr<A> test(new A);
boost::shared_ptr通过重载->(返回传入的指针),test的使用就如同一个指针。其实test是一个对象。
当发生引用时,boost::shared_ptr<A> test1 = test; test1与test共享构造的A指针,引用计算加一。当析够发生时,计算器减一,当计数器为0,删除内嵌指针。

常用的boost::shared_ptr函数有:
get() 获取裸指针
reset() 计数器减一

另外,boost::shared_ptr可以方便的和std::vector配合,除了不用担心节点的野指针等问题,还有一个比较有意思的功能。
class B : public A
{
virtual void process();
void do();
}
std::vector< boost::shared_ptr<A> > vect;
boost::shared_ptr<B> node = boost::shared_ptr<B>(new B);
vect.push_back(node);
vect[0]->do(); //可以很方便的访问B::do(),要知道do()并不是A的方法。
boost::shared_ptr有个一个缺点,就是不能从this指针构造。在boost库中,提供了一个解决方案。
#include <boost/enable_shared_from_this.hpp>
class C: public boost::enable_shared_from_this<C> //
{
}
这个情况出现在什么时候呢,如:
class D
{
public:
void Go(boost::shared_ptr<C> &d);
}
而D的Go方法在C中被使用,这个时候,就需要从this指针构造C的智能指针(boost::shared_from_this()方法提供)。当然,这种方法有一个前提,那就是C在外部的形态也是智能指针。

最后,对所有智能指针做一下简单的介绍吧。
auto_ptr 标准库中的智能指针。但是会转移所有权,如a = b时;内嵌的指针转移到b,智能指针a访问内嵌的指针则为空。
scoped_ptr 与auto_ptr类似,但是不允许复制;
intrusive_ptr是shared_ptr侵入式版本。使用情况,内部以及编写好了自己的内部引用计算器的代码,而又没有时间重写它。intrusive_ptr可以从this构造。
weak_ptr是智能指针shared_ptr的观察者。

对象的强引用和弱引用


如何保证一个对象在释放后不会再被访问? 最简单的是使用带引用计数的智能指针,但是强引用会导致对象不会被释放。那么弱引用如何?弱引用允许对象被释放,弱引用必须先转成强引用才能对对象进行操作,如果转换失败,则表示对象已经被删除。
正好看到一篇文章“当析构函数遇到多线程──C++ 中线程安全的对象回调”,其大意是使用shared_ptr和weak_ptr来完成。对象必须使用shared_ptr来引用,weak_ptr必须先转成shared_ptr才能对对象进行操作。
几个相关的智能指针封装类
auto_ptr   可以自动完成指针的释放。问题是,auto_ptr允许复制,并且内部对象的归属就自动转移到新生成的auto_ptr对象上,现有auto_ptr就不 能访问了,这很容易让人困惑和导致错误,也因此auto_ptr不能用于标准STL容器中(因为涉及元素拷贝)。  
scoped_ptr  跟auto_ptr的功能一样,但是不允许被复制,这样就避免了不小心导致的错误。
shared_ptr   跟auto_ptr和scoped_ptr不一样,shared_ptr允许复制,多个shared_ptr实例指向的是同一个内部对象,并通过引用计数来控制内部对象的生命周期。因为可以复制,所以shared_ptr可以用于标准STL容器中。
weak_ptr  跟 shared_ptr一样,weak_ptr 也允许复制。但与shared_ptr锁住内部对象的生命周期不一样的是,weak_ptr允许shared_ptr指向的对象被释放。在对内部对象操作 前,weak_ptr必须先转成shared_ptr(即先锁住内部对象避免被释放,如果锁定失败则意味着对象已经被释放了)。weak_ptr必须从 shared_ptr构造得来,因为需要共享一些数据,要不怎么能转换呢。
要保证对象的析构安全,必须统一通过shared_ptr和weak_ptr来引用对象,不能直接使用裸指针。 现在的问题是如何保证线程安全,特别是从weak_ptr转成shared_ptr的瞬间。
关键是如下操作引用计数的代码,add_ref_lock必须保证在use_count_ 不为零的时候加一成功!代码很精妙。
    bool add_ref_lock() // true on success
    {
        for( ;; )
        {
            long tmp = static_cast< long const volatile& >( use_count_ );
            if( tmp == 0 ) return false;
#if defined( BOOST_MSVC ) && BOOST_WORKAROUND( BOOST_MSVC, == 1200 )
            // work around a code generation bug
            long tmp2 = tmp + 1;
            if( BOOST_INTERLOCKED_COMPARE_EXCHANGE( &use_count_, tmp2, tmp ) == tmp2 - 1 ) return true;
#else
            if( BOOST_INTERLOCKED_COMPARE_EXCHANGE( &use_count_, tmp + 1, tmp ) == tmp ) return true;
#endif
        }
    }
    void release() // nothrow
    {
        if( BOOST_INTERLOCKED_DECREMENT( &use_count_ ) == 0 )
        {
            dispose();
            weak_release();
        }
    }

make_shared 和 allocate_shared 函数模板


Introduction 简介

使用 shared_ptr 可以消除对显式 delete 的使用,但是它没有提供避免显式 new 的支持。有用户反复地要求提供一个工厂函数,用于创建给定类型的对象并返回一个指向它的 shared_ptr. 除了方便使用和保持风格以外,这样的函数还具有异常安全性且明显更快,因为它可以对对象和相应的控制块两者同时使用单次的内存分配,以消除 shared_ptr 的构造过程中最大的一部分开销。这消除了对于 shared_ptr 的一个主要的抱怨。

头文件 <boost/make_shared.hpp> 提供了一组重载的函数模板,make_sharedallocate_shared, 它们解决了这些需要。make_shared 使用全局的 operator new 来分配内存,而 allocate_shared 则使用用户所提供的分配器,可以更好地进行控制。

选择 make_shared 这个名字的原因是,表达式 make_shared<Widget>() 可以大声地读出来,并且传达了准确的意义。

Synopsis 摘要

namespace boost {

template<typename T> class shared_ptr;

template<typename T>
shared_ptr<T> make_shared();

template<typename T, typename A>
shared_ptr<T> allocate_shared( A const & );

#if defined( BOOST_HAS_VARIADIC_TMPL ) && defined( BOOST_HAS_RVALUE_REFS ) // C++0x prototypes

template<typename T, typename... Args>
shared_ptr<T> make_shared( Args && ... args );

template<typename T, typename A, typename... Args>
shared_ptr<T> allocate_shared( A const & a, Args && ... args );

#else // no C++0X support

template<typename T, typename Arg1 >
shared_ptr<T> make_shared( Arg1 const & arg1 );
template<typename T, typename Arg1, typename Arg2 >
shared_ptr<T> make_shared( Arg1 const & arg1, Arg2 const & arg2 );
// ...
template<typename T, typename Arg1, typename Arg2, ..., typename ArgN >
shared_ptr<T> make_shared( Arg1 const & arg1, Arg2 const & arg2, ..., ArgN const & argN );

template<typename T, typename A, typename Arg1 >
shared_ptr<T> allocate_shared( A const & a, Arg1 const & arg1 );
template<typename T, typename A, typename Arg1, typename Arg2 >
shared_ptr<T> allocate_shared( Arg1 const & arg1, Arg2 const & arg2 );
// ...
template<typename T, typename A, typename Arg1, typename Arg2, ..., typename ArgN >
shared_ptr<T> allocate_shared( A const & a, Arg1 const & arg1, Arg2 const & arg2, ..., ArgN const & argN );

#endif
}

Free Functions 自由函数

template<class T, class... Args>
shared_ptr<T> make_shared( Args && ... args );
template<class T, class A, class... Args>
shared_ptr<T> allocate_shared( A const & a, Args && ... args );

要求:表达式 new( pv ) T( std::forward<Args>(args)... ) 是良好定义的,其中 pv 是一个 void*,指向适合保存一个类型为 T 的对象的一块内存A 应该是一个分配器,符合在C++标准的第20.1.5节(分配器要求)中的描述。A 的复制构造函数和析构函数不能抛出。

效果:分配一块可以保存一个类型为 T 的对象的内存,并通过 placement new 表达式 new( pv ) T()new( pv ) T( std::forward<Args>(args)... ) 在其中构造一个对象。allocate_shared 使用 a 的一个拷贝来分配内存。如果有异常抛出,则没有影响。

返回:一个 shared_ptr 实例,它保存并拥有这个新构造的类型为 T 的对象的地址。

后验条件:get() != 0 && use_count() == 1.

抛出:bad_alloc, 或是由 A::allocateT 的构造函数抛出的异常。

说明:该实现在单次内存分配中分配了所返回的 shared_ptr 以及一个类型为 T 的对象所要的内存。这提供了和介入式智能指针同样的效率。

如果你的编译支持右值引用和可变参数数量模板,则使用以上原型。它们完美地将 args 参数前转给 T 的构造函数。

否则,本实现将退回至将参数以常量引用的方式前转给 T 的构造函数。如果你需要传递一个非常量引用给 T 的构造函数,你可以通过将参数包装在一个 boost::ref 调用中来实现。另外,你被限制为最多使用9个参数(不计 allocate_shared 的分配器参数)。

Example 救命

boost::shared_ptr<std::string> x = boost::make_shared<std::string>("hello, world!");
std::cout << *x;

intrusive_ptr 类模板


Introduction 简介

intrusive_ptr 类模板存储一个指向带有侵入式引用计数的对象的指针。每一个新的 intrusive_ptr 实例都通过对函数 intrusive_ptr_add_ref 的无条件调用(将指针作为参数)增加引用计数。同样,当一个 intrusive_ptr 被销毁,它会调用 intrusive_ptr_release,这个函数负责当引用计数降为 0 时销毁这个对象。这两个函数的适当定义由用户提供。在支持 argument-dependent lookup (参数依赖查找)的编译器上,intrusive_ptr_add_refintrusive_ptr_release 应该和它们的参数定义在同一个名字空间中,否则,就定义名字空间 boost 中。

这个类模板以 T 为参数,T 是被指向的对象的类型。只要 T* 能被隐式地转换到 U*,则 intrusive_ptr<T> 就能被隐式地转换到 intrusive_ptr<U>

使用 intrusive_ptr 的主要原因是:

  • 一些已有的 frameworks 和操作系统提供带有侵入式引用计数的对象;
  • intrusive_ptr 的内存占用量和相应的裸指针一样。
  • intrusive_ptr<T> 能够从任意一个类型为 T * 的裸指针构造出来。

作为一个通用规则,如果 intrusive_ptr 不是很明显地比 shared_ptr 更加适合你的需要,请首先考虑基于 shared_ptr 的设计。

Synopsis 概要

namespace boost {

template<class T> class intrusive_ptr {

public:

typedef T element_type;

intrusive_ptr(); // never throws
intrusive_ptr(T * p, bool add_ref = true);

intrusive_ptr(intrusive_ptr const & r);
template<class Y> intrusive_ptr(intrusive_ptr<Y> const & r);

~intrusive_ptr();

intrusive_ptr & operator=(intrusive_ptr const & r);
template<class Y> intrusive_ptr & operator=(intrusive_ptr<Y> const & r);
template<class Y> intrusive_ptr & operator=(T * r);
 void reset(T * r);

T & operator*() const; // never throws
T * operator->() const; // never throws
T * get() const; // never throws

operator unspecified-bool-type() const; // never throws

void swap(intrusive_ptr & b); // never throws
};

template<class T, class U>
bool operator==(intrusive_ptr<T> const & a, intrusive_ptr<U> const & b); // never throws

template<class T, class U>
bool operator!=(intrusive_ptr<T> const & a, intrusive_ptr<U> const & b); // never throws

template<class T>
bool operator==(intrusive_ptr<T> const & a, T * b); // never throws

template<class T>
bool operator!=(intrusive_ptr<T> const & a, T * b); // never throws

template<class T>
bool operator==(T * a, intrusive_ptr<T> const & b); // never throws

template<class T>
bool operator!=(T * a, intrusive_ptr<T> const & b); // never throws

template<class T, class U>
bool operator<(intrusive_ptr<T> const & a, intrusive_ptr<U> const & b); // never throws

template<class T> void swap(intrusive_ptr<T> & a, intrusive_ptr<T> & b); // never throws

template<class T> T * get_pointer(intrusive_ptr<T> const & p); // never throws

template<class T, class U>
intrusive_ptr<T> static_pointer_cast(intrusive_ptr<U> const & r); // never throws

template<class T, class U>
intrusive_ptr<T> const_pointer_cast(intrusive_ptr<U> const & r); // never throws

template<class T, class U>
intrusive_ptr<T> dynamic_pointer_cast(intrusive_ptr<U> const & r); // never throws

template<class E, class T, class Y>
std::basic_ostream<E, T> & operator<< (std::basic_ostream<E, T> & os, intrusive_ptr<Y> const & p);

}

Members 成员

element_type 元素类型

typedef T element_type;

提供模板参数 T 的类型。

constructors 构造函数

intrusive_ptr(); // never throws

后置条件:get() == 0

抛出:无。

intrusive_ptr(T * p, bool add_ref = true);

作用:if(p != 0 && add_ref) intrusive_ptr_add_ref(p);

后置条件:get() == p

intrusive_ptr(intrusive_ptr const & r);
template<class Y> intrusive_ptr(intrusive_ptr<Y> const & r);

作用:if(r.get() != 0) intrusive_ptr_add_ref(r.get());

后置条件:get() == r.get()

destructor 析构函数

~intrusive_ptr();

作用:if(get() != 0) intrusive_ptr_release(get());

assignment 赋值

intrusive_ptr & operator=(intrusive_ptr const & r);
template<class Y> intrusive_ptr & operator=(intrusive_ptr<Y> const & r);
intrusive_ptr & operator=(T * r);

作用:等价于 intrusive_ptr(r).swap(*this)

返回:*this

reset 重置

void reset(T * r);

作用:等价于 intrusive_ptr(r).swap(*this).

indirection 间接引用

T & operator*() const; // never throws

条件:get() != 0

返回:*get()

抛出:无。

T * operator->() const; // never throws

条件:get() != 0

返回:get()

抛出:无。

get 取得

T * get() const; // never throws

返回:所存储的指针。

抛出:无。

conversions 转换

operator unspecified-bool-type () const; // never throws

返回:一个未确定的值,在需要布尔值的上下文中,它等价于 get() != 0

抛出:无。

注意:这一转换操作符允许将 intrusive_ptr 对象用于需要布尔值的上下文中,就像 if (p && p->valid()) {}。实际目标类型通常是一个指向成员函数的指针,消除了很多隐式转换的陷阱。

swap 交换

void swap(intrusive_ptr & b); // never throws

作用:交换两个智能指针中的内容。

抛出:无。

Free Functions 自由函数

comparison 比较

template<class T, class U>
bool operator==(intrusive_ptr<T> const & a, intrusive_ptr<U> const & b); // never throws

返回:a.get() == b.get()

抛出:无。

template<class T, class U>
bool operator!=(intrusive_ptr<T> const & a, intrusive_ptr<U> const & b); // never throws

返回:a.get() != b.get()

抛出:无。

template<class T, class U>
bool operator==(intrusive_ptr<T> const & a, U * b); // never throws

返回:a.get() == b

抛出:无。

template<class T, class U>
bool operator!=(intrusive_ptr<T> const & a, U * b); // never throws

返回:a.get() != b

抛出:无。

template<class T, class U>
bool operator==(T * a, intrusive_ptr<U> const & b); // never throws

返回:a == b.get()

抛出:无。

template<class T, class U>
bool operator!=(T * a, intrusive_ptr<U> const & b); // never throws

返回:a != b.get()

抛出:无。

template<class T, class U>
bool operator<(intrusive_ptr<T> const & a, intrusive_ptr<U> const & b); // never throws

返回:std::less<T *>()(a.get(), b.get())

抛出:无。

注意:允许 intrusive_ptr 对象在关联式容器中作为键值使用。

swap 交换

template<class T>
void swap(intrusive_ptr<T> & a, intrusive_ptr<T> & b); // never throws

作用:等价于 a.swap(b)

抛出:无。

注意:std::swap 的接口匹配。为泛型编程提供帮助。

get_pointer 取得指针

template<class T>
T * get_pointer(intrusive_ptr<T> const & p); // never throws

返回:p.get()

抛出:无。

注意:为泛型编程提供帮助,用于 mem_fn

static_pointer_cast

template<class T, class U>
intrusive_ptr<T> static_pointer_cast(intrusive_ptr<U> const & r); // never throws

返回:intrusive_ptr<T>(static_cast<T*>(r.get()))

抛出:无。

const_pointer_cast

template<class T, class U>
intrusive_ptr<T> const_pointer_cast(intrusive_ptr<U> const & r); // never throws

返回:intrusive_ptr<T>(const_cast<T*>(r.get()))

抛出:无。

dynamic_pointer_cast

template<class T, class U>
intrusive_ptr<T> dynamic_pointer_cast(intrusive_ptr<U> const & r);

返回:intrusive_ptr<T>(dynamic_cast<T*>(r.get()))

抛出:无。

operator<<

template<class E, class T, class Y>
std::basic_ostream<E, T> & operator<< (std::basic_ostream<E, T> & os, intrusive_ptr<Y> const & p);

作用:os << p.get();

返回:os


intrusive_ptr

头文件: "boost/intrusive_ptr.hpp"

intrusive_ptrshared_ptr的插入式版本。有时我们必须使用插入式的引用计数智能指针。典型的情况是对于那些已经写好了内部引用计数器的代码,而我们又没有时间去重写它(或者已经不能获得那些代码了)。另一种情况是要求智能指针的大小必须与裸指针大小严格相等,或者shared_ptr的引用计数器分配严重影响了程序的性能(我可以肯定这是非常罕见的情况!)。从功能的观点来看,唯一需要插入式智能指针的情况是,被指类的某个成员函数需要返回this,以便它可以用于另一个智能指针(事实上,也有办法使用非插入式智能指针来解决这个问题,正如我们在本章前面看到的)。intrusive_ptr 不同于其它智能指针,因为它要求你来提供它所要的引用计数器。

intrusive_ptr 递增或递减一个非空指针上的引用计数时,它是通过分别调用函数 intrusive_ptr_add_refintrusive_ptr_release来完成的。这两个函数负责确保引用计数的正确性,并且负责在引用计数降为零时删除指针。因此,你必须为你的类重载这两个函数,正如我们后面将看到的。

以下是intrusive_ptr的部分摘要,只列出了最重要的函数。

namespace boost {

template<class T> class intrusive_ptr {
public:
intrusive_ptr(T* p,bool add_ref=true);

intrusive_ptr(const intrusive_ptr& r);

~intrusive_ptr();

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

operator unspecified-bool-type() const;
};

template <class T> T* get_pointer(const intrusive_ptr<T>& p);

template <class T,class U> intrusive_ptr<T>
static_pointer_cast(const intrusive_ptr<U>& r);
}

成员函数

intrusive_ptr(T* p,bool add_ref=true);

这个构造函数将指针p保存到*this中。如果 p 非空,并且 add_reftrue, 构造函数将调用 intrusive_ptr_add_ref(p). 如果 add_reffalse, 构造函数则不调用 intrusive_ptr_add_ref. 如果intrusive_ptr_add_ref会抛出异常,则构造函数也会。

intrusive_ptr(const intrusive_ptr& r);

该复制构造函数保存一份r.get()的拷贝,并且如果指空非空则用它调用 intrusive_ptr_add_ref 。这个构造函数不会抛出异常。

~intrusive_ptr();

如果保存的指针为非空,则 intrusive_ptr 的析构函数会以保存的指针为参数调用函数 intrusive_ptr_releaseintrusive_ptr_release 负责递减引用计数并在计数为零时删除指针。这个函数不会抛出异常。

T& operator*() const;

解引用操作符返回所存指针的解引用。如果所存指针为空则会导致未定义行为。你应该确认intrusive_ptr有一个非空的指针,这可以用函数 get 实现,或者在Boolean上下文中测试 intrusive_ptr 。解引用操作符不会抛出异常。

T* operator->() const;

这个操作符返回保存的指针。在引用的指针为空时调用这个操作符会有未定义行为。这个操作符不会抛出异常。

T* get() const;

这个成员函数返回保存的指针。它可用于你需要一个裸指针的时候,即使保存的指针为空也可以调用。这个函数不会抛出异常。

operator unspecified-bool-type() const;

这个类型转换函数返回一个可用于布尔表达式的类型,而它绝对不是 operator bool, 因为那样会允许一些必须要禁止的操作。这个转换允许intrusive_ptr在一个布尔上下文中被测试,例如,if (p), p 是一个 intrusive_ptr. 这个转换函数当intrusive_ptr引向一个非空指针时返回True ; 否则返回 false. 这个转换函数不会抛出异常。

普通函数

template <class T> T* get_pointer(const intrusive_ptr<T>& p);

这个函数返回 p.get(), 它主要用于支持泛型编程。[10] 它也可以用作替代成员函数 get, 因为它可以重载为可以与裸指针或第三方智能指针类一起工作。有些人宁愿用普通函数而不用成员函数。[11] 这个函数不会抛出异常。

[10] 这种函数被称为 shims. 见参考书目的 [12] 。

[11]这种想法是出于以下原因,使用智能指针的成员函数时,很难分清它是操作智能指针还是操作它所指向的对象。例如, p.get()p->get() 有完全不同的意思,不认真看还很难区别,而 get_pointer(p)p->get() 则一看就知道不一样。对于你来说这是不是问题,主要取决于你的感觉和经验。

template <class T,class U>
intrusive_ptr<T> static_pointer_cast(const intrusive_ptr<U>& r);

这个函数返回 intrusive_ptr<T>(static_cast<T*>(r.get())). 和 shared_ptr不一样,你可以对保存在intrusive_ptr中的对象指针安全地使用static_cast。但是你可能出于对智能指针类型转换的用法一致性而想使用这个函数。static_pointer_cast 不会抛出异常。

用法

使用intrusive_ptr与使用shared_ptr相比,有两个主要的不同之处。第一个是你需要提供引用计数的机制。第二个是把this当成智能指针是合法的[12],正如我们即将看到的,有时候这样很方便。注意,在多数情况下,应该使用非插入式的 shared_ptr.

[12] 你不能用shared_ptr 来做到这一点,如果没有进行特殊处理的话,如 enable_shared_from_this.

要使用 boost::intrusive_ptr, 要包含 "boost/intrusive_ptr.hpp" 并定义两个普通函数 intrusive_ptr_add_refintrusive_ptr_release. 它们都要接受一个参数,即指向你要使用intrusive_ptr的类型的指针。这两个函数的返回值被忽略。通常的做法是,泛化这两个函数,简单地调用被管理类型的成员函数去完成工作(例如,调用 add_refrelease)。如果引用计数降为零,intrusive_ptr_release 应该负责释放资源。以下是你应该如何实现这两个泛型函数的示范:

template <typename T> void intrusive_ptr_add_ref(T* t) {
t->add_ref();
}

template <typename T> void intrusive_ptr_release(T* t) {
if (t->release()<=0)
delete t;
}

注意,这两个函数应该定义在它们的参数类型所在的作用域内。这意味着如果这个函数接受的参数类型来自于一个名字空 间,则函数也必须定义在那里。这样做的原因是,函数的调用是非受限的,即允许采用参数相关查找,而如果有多个版本的函数被提供,那么全部名字空间肯定不是 放置它们的好地方。我们稍后将看到一个关于如何放置它们的例子,但首先,我们需要提供某类的引用计数器。

提供一个引用计数器

现在管理用的函数已经定义了,我们必须要提供一个内部的引用计数器了。在本例中,引用计数是一个初始化为零的私有数据成员,我们将公开 add_refrelease 成员函数来操作它。add_ref 递增引用计数而 release 递减它[13]。 我们可以增加一个返回引用计数当前值的成员函数,但release也可以做到这一点。下面的基类,reference_counter, 提供了一个计数器以及 add_refrelease 成员函数,我们可以简单地用继承来为一个类增加引用计数了。

[13] 注意,在多线程环境下,对保持引用计数的变量的任何操作都必须同步化。

class reference_counter {
int ref_count_;
public:
reference_counter() : ref_count_(0) {}

virtual ~reference_counter() {}

void add_ref() {
++ref_count_;
}

int release() {
return --ref_count_;
}

protected:
reference_counter& operator=(const reference_counter&) {
// 无操作
return *this;
}
private:
// 禁止复制构造函数
reference_counter(const reference_counter&);
};

reference_counter的析构函数声明为虚拟的原因是这个类将被公开继承,有可能会使用一个reference_counter指针来delete派生类。我们希望删除操作能够正确地调用派生类的析构函数。实现非常简单:add_ref 递增引用计数,release 递减引用计数并返回它。要使用这个引用计数,要做的就是公共地继承它。以下是一个类 some_ class ,包含一个内部引用计数,并使用 intrusive_ptr

#include <iostream>
#include "boost/intrusive_ptr.hpp"

class some_class : public reference_counter {
public:
some_class() {
std::cout << "some_class::some_class()\n";
}

some_class(const some_class& other) {
std::cout << "some_class(const some_class& other)\n";
}

~some_class() {
std::cout << "some_class::~some_class()\n";
}
};

int main() {
std::cout << "Before start of scope\n";
{
boost::intrusive_ptr<some_class> p1(new some_class());
boost::intrusive_ptr<some_class> p2(p1);
}
std::cout << "After end of scope \n";
}

为了显示 intrusive_ptr以及函数 intrusive_ptr_add_refintrusive_ptr_release 都正确无误,以下是这个程序的运行输出:

Before start of scope
some_class::some_class()
some_class::~some_class()
After end of scope

intrusive_ptr 为我们打点一切。当第一个 intrusive_ptr p1 创建时,它传送了一个some_class的新实例。intrusive_ptr 构造函数实际上有两个参数,第二个是一个 bool ,表示是否要调用 intrusive_ptr_add_ref 。由于这个参数的缺省值是 True, 所以在构造 p1时,some_class实例的引用计数变为1。然后,第二个 intrusive_ptr, p2初构造。它是从 p1复制构造的,当 p2 看到 p1 是引向一个非空指针时,它调用 intrusive_ptr_add_ref. 引用计数变为2。然后,两个 intrusive_ptr都离开作用域了。首先, p2 被销毁,析构函数调用 intrusive_ptr_release. 它把引用计数减为1。然后,p1 被销毁,析构函数再次调用 intrusive_ptr_release ,导致引用计数降为0;这使得我们的intrusive_ptr_releasedelete 该指针。你可能注意到 reference_counter 的实现不是线程安全的,因此不能用于多线程应用,除非加上同步化。

比起依赖于intrusive_ptr_add_refintrusive_ptr_release的泛型实现,我们最好有一些直接操作基类(在这里是 reference_counter)的函数。这样做的优点在于,即使从reference_counter派生的类定义在其它的名字空间,intrusive_ptr_add_refintrusive_ptr_release 也还可以通过ADL (参数相关查找法)找到它们。修改reference_counter的实现很简单。

class reference_counter {
int ref_count_;
public:
reference_counter() : ref_count_(0) {}

virtual ~reference_counter() {}

friend void intrusive_ptr_add_ref(reference_counter* p) {
++p->ref_count_;
}

friend void intrusive_ptr_release(reference_counter* p) {
if (--p->ref_count_==0)
delete p;
}

protected:
reference_counter& operator=(const reference_counter&) {
// 无操作
return *this;
}
private:
// 禁止复制构造函数
reference_counter(const reference_counter&);
};

this 用作智能指针

总的来说,提出一定要用插入式引用计数智能指针的情形是不容易的。大多数情况下,但不是全部情况下,非插入式智能指针都可以解决问题。但是,有一种情形使用插入式引用计数会更容易:当你需要从一个成员函数返回 this ,并把它存入另一个智能指针。当从一个被非插入式智能指针所拥有的类型返回 this时, 结果是有两个不同的智能指针认为它们拥有同一个对象,这意味着它们会在某个时候一起试图删除同一个指针。这导致了两次删除,结果可能使你的应用程序崩溃。 必须有什么办法可以通知另一个智能指针,这个资源已经被一个智能指针所引用,这正好是内部引用计数器(暗地里)可以做到的。由于 intrusive_ptr 的逻辑不直接对它们所引向的对象的内部引用计数进行操作,这就不会违反所有权或引用计数的完整性。引用计数只是被简单地递增。

让我们先看一下一个依赖于boost::shared_ptr来共享资源所有权的实现中潜在的问题。它基于本章前面讨论enable_shared_from_this时的例子。

#include "boost/shared_ptr.hpp"

class A;

void do_stuff(boost::shared_ptr<A> p) {
// ...
}

class A {
public:
call_do_stuff() {
shared_ptr<A> p(???);
do_stuff(p);
}
};

int main() {
boost::shared_ptr<A> p(new A());
p->call_do_stuff();
}

A 要调用函数 do_stuff, 但问题是 do_stuff 要一个 shared_ptr<A>, 而不是一个普通的A指针。因此,在 A::call_do_stuff里,应该如何创建 shared_ptr ?现在,让我们重写 A ,让它兼容于 intrusive_ptr, 通过从 reference_counter派生,然后我们再增加一个 do_stuff的重载版本,接受一个 intrusive_ptr<A>类型的参数。

#include "boost/intrusive_ptr.hpp"

class A;

void do_stuff(boost::intrusive_ptr<A> p) {
// ...
}

void do_stuff(boost::shared_ptr<A> p) {
// ...
}

class A : public reference_counter {
public:
void call_do_stuff() {
do_stuff(this);
}
};

int main() {
boost::intrusive_ptr<A> p(new A());
p->call_do_stuff();
}

如你所见,在这个版本的 A::call_do_stuff里,我们可以直接把 this 传给需要一个 intrusive_ptr<A>的函数,这是由于 intrusive_ptr的类型转换构造函数。

最后,这里有一个特别的地方:现在 A 可以支持 intrusive_ptr了,我们也可以创建一个包装intrusive_ptrshared_ptr,这们我们就可以调用原来版本的 do_stuff, 它需要一个 shared_ptr<A> 作为参数。假如你不能控制 do_stuff的源码,这可能是你要解决的一个非常真实的问题。这次,还是用定制删除器的方法来解决,它需要调用 intrusive_ptr_release. 下面是一个新版本的 A::call_do_stuff.

void call_do_stuff() {
intrusive_ptr_add_ref(this);
boost::shared_ptr<A> p(this,&intrusive_ptr_release<A>);
do_stuff(p);
}

真是一个漂亮的方法。当没有 shared_ptr剩下时,定制的删除器被调用,它调用 intrusive_ptr_release, 递减A的内部引用计数。注意,如果 intrusive_ptr_add_refintrusive_ptr_release 被实现为直接操作 reference_counter, 你就要这样来创建 shared_ptr

boost::shared_ptr<A> p(this,&intrusive_ptr_release);

支持不同的引用计数器

我们前面提过可以为不同的类型支持不同的引用计数。这在集成已有的采用不同引用计数机制的类时是有必要的(例如,第三方的类使用它们自己版本的引用计数器)。又或者对于资源的释放有不同的需求,如调用delete以外的另一个函数。如前所述,对 intrusive_ptr_add_refintrusive_ptr_release 的调用是非受限的。这意味着在名字查找时要考虑参数(指针的类型)的作用域,从而这些函数应该与它们操作的类型定义在同一个作用域。如果你在全局名字空间里实现 intrusive_ptr_add_refintrusive_ptr_release 的泛型版本,你就不能在其它名字空间中再创建泛型版本了。例如,如果一个名字空间需要为它的所有类型定义一个特殊的版本,特化版本或重载版本必须提供给每 一个类型。否则,全局名字空间中的函数就会引起歧义。因此在全局名字空间中提供泛型版本不是一个好主意,而在其它名字空间中提供则可以。

既然我们已经用基类reference_counter实现了引用计数器,那么在全局名字空间中提供一个接受reference_counter*类型的参数的普通函数应该是一个好主意。这还可以让我们在其它名字空间中提供泛型重载版本而不会引起歧义。例如,考虑my_namespace名字空间中的两个类 another_classderived_class

namespace my_namespace {
class another_class : public reference_counter {
public:
void call_before_destruction() const {
std::cout <<
"Yes, I'm ready before destruction\n";
}
};

class derived_class : public another_class {};

template <typename T> void intrusive_ptr_add_ref(T* t) {
t->add_ref();
}

template <typename T> void intrusive_ptr_release(T* t) {
if (t->release()<=0) {
t->call_before_destruction();
delete t;
}
}
}

这里,我们实现了intrusive_ptr_add_refintrusive_ptr_release的泛型版本。因此我们必须删掉在全局名字空间中的泛型版本,把它们替换为以一个reference_counter指针为参数的非模板版本。或者,我们干脆从全局名字空间中删掉这些函数,也可以避免引起混乱。对于这两个类 my_namespace::another_classmy_namespace::derived_class, 将调用这个特殊版本(那个调用了它的参数的成员函数 call_before_destruction 的版本)。其它类型或者在它们定义所在的名字空间中有相应的函数,或者使用全局名字空间中的版本,如果有的话。下面程序示范了这如何工作:

int main() {
boost::intrusive_ptr<my_namespace::another_class>
p1(new my_namespace::another_class());
boost::intrusive_ptr<A>
p2(new good_class());
boost::intrusive_ptr<my_namespace::derived_class>
p3(new my_namespace::derived_class());
}

首先,intrusive_ptr p1 被传入一个新的 my_namespace::another_class实例。在解析对 intrusive_ptr_add_ref的调用时,编译器会找到 my_namespace里的版本,即 my_namespace::another_class* 参数所在名字空间。因而,为那个名字空间里的类型所提供的泛型函数会被正确地调用。在查找 intrusive_ptr_release时也是同样。然后,intrusive_ptr p2 被创建并被传入一个类型A (我们早前创建的那个类型)的指针。那个类型是在全局名字空间里的,所以当编译器试图去找到函数 intrusive_ptr_add_ref的最佳匹配时,它只会找到一个版本,即接受reference_counter指针类型的那个版本(你应该记得我们已经从全局名字空间中删掉了泛型版本)。因为 A 公共继承自 reference_counter, 通过隐式类型转换就可以进行正确的调用。最后,my_namespace 里的泛型版本被用于类 my_namespace::derived_class; 这与 another_class例子中的查找是一样的。

这里最重要的教训是,在实现函数 intrusive_ptr_add_refintrusive_ptr_release时,它们应该总是定义在它们操作的类型所在的名字空间里。从设计的角度来看,这也是完美的,把相关的东西放在一起,这有助于确保总是调用正确的版本,而不用担心是否有多个不同的实现可供选择。

总结

在多数情况下,你不应该使用 boost::intrusive_ptr, 因为共享所有权的功能已在 boost::shared_ptr中提供,而且非插入式智能指针比插入式智能指针更灵活。但是,有时候也会需要插入式的引用计数,可能是由于旧的代码,或者是为了与第三方的类进行集成。当有这种需要时,可以用 intrusive_ptr ,它具有与其它Boost智能指针相同的语义。如果你使用过其它的Boost智能指针,你就会发现不论是否插入式的,所有智能指针都有一致的接口。使用intrusive_ptr的类必须可以提供引用计数。ntrusive_ptr 通过调用两个函数,intrusive_ptr_add_refintrusive_ptr_release来管理引用计数;这两个函数必须正确地操作插入式的引用计数,以保证 intrusive_ptr正确工作。在使用intrusive_ptr的类中已经内置有引用计数的情况下,实现对intrusive_ptr的支持就是实现这两个函数。有些情况下,可以创建这两个函数的参数化版本,然后对所有带插入式引用计数的类型使用相同的实现。多数时候,声明这两个函数的最好的地方就是它们所支持的类型所在的名字空间。

在以下情况时使用 intrusive_ptr

  • 你需要把 this 当作智能指针来使用。

  • 已有代码使用或提供了插入式的引用计数。

  • 智能指针的大小必须与裸指针的大小相等。

    本站是提供个人知识管理的网络存储空间,所有内容均由用户发布,不代表本站观点。请注意甄别内容中的联系方式、诱导购买等信息,谨防诈骗。如发现有害或侵权内容,请点击一键举报。
    转藏 分享 献花(0

    0条评论

    发表

    请遵守用户 评论公约

    类似文章 更多