智能指针的研究

    由于C++中堆内存是由程序员自己管理,要把对象分配在堆中,必须手动调用new,并且必须记住调用delete释放之前分配的内存。大量的new和delete必定会造成代码十分混乱,并且很容易出现悬空指针和内存泄漏等问题,smart pointer智能指针就是帮助程序员更好地处理这些问题。
    研究了3个智能指针实现的框架(boost,android,还有一个是内部库),每个框架的实现大体思路都一致,但在一些个别地方有细微的差别,当然,这3个智能指针的实现都针对自身不同的需求做了优化处理。

总体实现思路
    首先智能指针的总体思路就是利用引用计数来判断对象是否仍然在使用,当引用计数为0,则删除该对象。但智能指针有一个很大的问题,就是循环引用导致两者都无法释放,造成内存泄漏,因此就需要引入另外一个引用计数来解除这种这个循环,这就是弱指针,上面那个决定对象生命周期的指针也叫做强指针。
    智能指针的实现,可以分为三个功能模块。
    首先,第一个就是引用计数模块,这个模块的实现通常需要整形数值来记录当前对象被引用的次数,根据上面的讨论,强/弱指针总共需要两个整形数值,另外考虑到多线程访问问题,需要使用原子增减,另外这个整形数值的存放地方也是可以值得思考,例如可以对于指向同一个对象的智能指针内部都存放一个引用,都指向同一个引用变量;又或者可以存放在对象的类内部,并提供方法去改变引用变量的值。
    第二个就是,指针的操作符重载问题。这个其实也是实现一个拥有指针行为的自定义类型的问题。当然,由于我们有强/弱指针,因此需要考虑到两种指针之间的赋值问题。
    第三个,就是内存分配问题,简单来说,就是可替换的内存分配机制。一般来说都是利用new/delete直接向系统分配堆上的空间来保留对象,但如果想减少内存碎片,提高分配效率,可以考虑使用内存池来替代直接分配。
    除此之外,也需要有一些要注意的地方,比如说这个智能指针是否数组类型,如果支持,则注意在delete的时候,对于数组类型要注意使用delete[]操作符。另外也要弱指针转换成强指针问题,如强指针已经全部释放,当前对象事实上已经无效等情况。

框架具体实现
    对于以上设计需求,三个不同的实现框架给出了不同的答案。
1、引用计数模块。
    boost的实现是把引用计数保留在指针内部,每个指针都指向同一个引用计数;而android的sp/wp,以及内部库实现则是把引用计数保留在对象的内部。大致代码如下:

//boost:
template<class T>
class shared_ptr
{
    //...


private:
    shared_count pn;
}


class shared_count
{
    //...
private:
    //sp_count_base内部
    sp_count_base *pi;
}


class sp_counted_base
{
private:
    long use_count_;
    long weak_count_;
}

    可以看到boost内部的share_count类就是引用计数模块,内部的sp_count_base就是具体的实现,指向同一对象的shared_ptr保证内部的sp_count_base指向同一个引用计数对象。weak_ptr的实现与shared_ptr类似,使用的是weak_count类,内部同样保留一个sp_count_base的指针,此处忽略。
    
    再来看看android和内部库的实现。


//android:
class RefBase
{
private:
    weakref_type* mrefs
}


class weakref_impl : public weakref_type
{
public:
    volatile int32_t mStrong;
    volatile int32_t mWeak;
}

    需要把具体对象继承自RefBase后,该对象才可赋值给智能指针。可以看到RefBase内部保留的weakref_type就是事实上的引用计数模块。

    事实上,这三种不同的实现里,还可以看到一些不同之处。例如,boost的这种实现,使得智能指针支持数组,但android和内部库实现则不支持(由于两种实现都需要具体对象继承自某个类,这就限制了数组、基本类型等不可能使指针模版实例化)。由于boost要支持数组,因此其单独添加了对数组类型偏特化,调用delete[]操作符释放对象。boost的实现了对于所有类型都使用智能指针的需求,另外,事实上boost内部在引用计数的具体实现模块(上面被省略的继承自sp_count_base的sp_counted_impl_**的类),重载new/delete操作符,可以转换为使用boost内部的quick_allocator来分配引用计数模块。
    另外,内部库和android的实现也有另外的不同之处。android要求类必须要显式继承自RefBase,并且需要显式调用new分配对象。对于内部库,则把这个实现细节给隐藏起来,并且构造对象的细节也放到了内部,这样就可以方便替换new/delete分配释放对象。唯一的不足之处恐怕就是直接在必须要在栈上构造CComOBj对象,这样对于先前使用了new/delete分配对象的程序来说,要移植该库来说可能会增加一些麻烦。
    指向同一对象的强指针的生命周期都一定会比指向对象要长,这是由于所有强指针被释放的时候,被指向的对象才会被析构。但对于弱指针就不同,弱指针的生命周期可能会比指向对象要长,由于弱指针主要是为了破除强指针的引用循环,对象的生命周期是strong_count(强引用计数)决定的,引用计数的生命周期则是由所有的智能指针决定的,只有当最后一个智能指针析构的时候,才可以删除引用计数模块。为了能够维护好对象以及引用计数模块的生命周期,可以这样做,首先,弱指针可能比对象生命周期要长,为了弱指针能够正确使用,一定要分配在堆上。另外,要保证strong_count<=weak_count,这样就可以当weak_count(弱引用计数)为0的时候,删除该引用计数模块。

2、操作符重载。
    事实上三者实现都是大致相同,重载*,->,=操作符,并且注意调用addStrongRef,decStrongRef,addWeakRef,decWeakRef(对于三种实现,这里暂时同一表达为增加/释放强引用计数,增加/释放弱引用计数)的适时调用。
    另外,这里还要考虑弱指针的职责。boost库把弱指针定义一种专门辅助强指针打破引用循环的指针功能类,因此,boost库的弱指针没有重载任何有关raw指针的操作符,并且只能够经过lock操作提升至强指针才可以访问弱指针的对象。android实现的弱指针在此基础上,也会对象的生命周期进行管理,也就是说,如果分配出来的对象,只有弱指针引用过,当所有弱指针被析构的时候,该对象就同时会被析构(boost库不会同时也不可能出现,因为没有重载对应操作符)。
    对于addStrongRef,decStrongRef,addWeakRef,decWeakRef的实现。由于内部的引用计数模块都是智能指针内部new分配在堆上,因此必须要手动释放。由于三种实现都有两个不同的计数strong_count,weak_count,分配对应强指针和弱指针的引用计数。这时要考虑可能出现只有强指针,或者只有弱指针,或者同时存在强、弱指针指向一个对象的情况。
    其实,必须要保证的是strong_count<=weak_count,当strong_count=0的时候要删除对象,weak_count=0的时候删除引用计数模块。
    对于boost,首先引用计数模块只能在强指针内部进行初始化,同时strong_count和weak_count的值为1。然后addStrongRef的时候只增加strong_count的值,decStrongRef的时候减去strong_count的值,并且判断,如果刚好strong_count为0的时候,同时调用decWeakRef,decWeakRef会当weak_count为0的时候,删除引用计数模块。由于weak_count初始化值为1,因此始终保证了引用模块的有效性。
    android由于允许弱指针只有弱指针指向对象,因此有些特殊。只有弱指针的情况下,当所有弱指针都被释放的时候,同时会释放指向的对象(在代码里貌似提供了方法来改变这一行为,即不释放对象,但发现已经被移除)。另外android在addStrongRef,decStrongRef的时候都会同时在内部分别调用addWeakRef,decWeakRef,这样就保持weak_count>=strong_count,这样就可以判断当真正当weak_count == 0的时候释放掉引用计数模块。

3、内存分配问题。
    boost库对于引用计数模块分配提供了一个额外的实现类来管理分配和释放(sp_counted_impl_xx继承自sp_counted_base)。sp_counted_impl_xx主要解决了一些内存分配和回收的问题。sp_counted_impl_xx通过重载new和delete操作符来改变对象分配和回收行为。sp_counted_impl_xx允许通过定义宏来在new和delete操作符里使用内部的quick_allocator来改变分配引用计数模块的行为,quick_allocator通过简单的预分配,内存对齐,还有链表保留回收内存重复利用等机制进行内存管理。另外delete操作符还会根据是否数组调用对应的delete析构语法。
    android没有提供这个内存分配机制。但提供了另外一些跟踪指针分配和释放的代码。由于引用计数模块在RefBase类内部,因此可以在addStrongRef,decStrongRef,addWeakRef,decWeakRef等方法对当前引用对象以及智能指针进行跟踪。这样能够随时动态打印之前所有的分配和回收信息方便对象分配状态进行诊断。另外,android为了能够延迟初始化时机或者希望在析构之前执行一些清理操作,因此提供了onFirstRef、onLastStrongRef、onLastWeakRef等函数重载。

一个简单的实现
    针对一般的应用情况,设计出一个简单的实现。首先为了简化设计,智能指针不支持数组,因此引用计数模块保存在对象派生类里面,另外可以在派生类内部添加内存管理、分配回收追踪等机制。然后弱指针不直接与对象交互,只能通过lock操作转换成强指针才可以访问对象指针。
    具体设计如下:
template<typename T>
class RefWrppaer 
{
    //增加类数量,但能够方便使用并且能够支持第三方类扩展
    template<typename Base>
    class RefBase : public Base
    {
    public:
        //以下这4个方法内部可以通过id(wp/sp的指针值)与自己的指针关联起来
        //方便进行跟踪
        void addStrongRef(const void *id);
        void decStrongRef(const void *id);

    
        void addWeakRef(const void *id);
        void decWeakRef(const void *id);

        //可选
        //重载new/delete,提供内存管理
        void * operator new( std::size_t );
        void operator delete( void * p );
    private:
        int strong_count;   //初始化为1
        int weak_count;     //初始化为1
    }


    //提供多参数版本
    T* wrap(T* base)
    {
        return new RefBase<T>;
    }
}


template<typename T>
clas sp
{
public:
    sp() : m_ptr(0) { }
    sp(T* other);
    sp(const sp<T>& other);
    template<typename U> sp(U* other);
    template<typename U> sp(const sp<U>& other);

    inline  T&      operator* () const  { return *m_ptr; }
    inline  T*      operator-> () const { return m_ptr;  }
    inline  T*      get() const         { return m_ptr; }


    sp& operator = (T* other);
    sp& operator = (const sp<T>& other);

    template<typename U> sp& operator = (const sp<U>& other);
    template<typename U> sp& operator = (U* other);


    //operator == / != / > / < / >= / <=


private:
    T* m_ptr;
}


templeate<typename T>
class wp
{
    //wp没有任何与raw pointer接触的操作符
public:
    wp() : m_ptr(0) { }
    wp(T* other);
    wp(const wp<T>& other);
    wp(const sp<T>& other);
    template<typename U> wp(U* other);
    template<typename U> wp(const sp<U>& other);
    template<typename U> wp(const wp<U>& other);


    wp& operator = (const sp<T>& other);
    wp& operator = (const wp<T>& other);


    template<typename U> wp& operator = (const sp<U>& other);
    template<typename U> wp& operator = (const wp<U>& other);


    //唯一可以访问对象的方法。
    //不一定保证返回对象有效
    sp<T> lock() const;


    //operator == / != / > / < / >= / <=


private:
    T* m_ptr;
}





    其中RefWrppaer属于一个工厂类,主要负责构造和派生类对象,虽然会增加类对象数量,但这样就可以方便支持第三方类扩展。


具体参考:
android的智能指针实现:
4.3代码目录:
frameworks/native/include/utils/RefBase.h
frameworks/native/libs/utils/RefBase.cpp
frameworks/native/include/utils/StrongPointer.h


boost实现目录:
smart_ptr/weak_ptr.hpp
smart_ptr/detail/shared_count.hpp
smart_ptr/detail/sp_counted_impl.hpp
smart_ptr/detail/sp_counted_base_w32.hpp
smart_ptr/shared_ptr.hpp













评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值