轻量级共享对象的灵巧指针的实现

轻量级共享对象的灵巧指针的实现

    毫无疑问,shared_ptr的功能不可谓不强大,设计不可谓不精巧,它的抽象级别不是一般的高,不仅要管理一般的C++内存资源,更染指其他的非C++资源,比如文件、比如连接、……,只要给它一个支点(释放资源的函数),不仅如此,还能顽强地生存于各种恶劣的环境,好比多线程、引用循环。当然,代价是有的,它背地里做了很多不为人知的勾当,表面上仅仅一行的带有构造函数shared_ptr的定义代码,编译器却要很无奈地生成一个莫明其妙的多态模板类(_Ref_count_base的继承类,带有虚函数表,意味着不能内联,用以在恰当的时机,释放资源),更别提要多了一堆指令,当然,在当今硬件性能蓬勃发展的美好时代,这点代价根本就不算什么,比之于那些什么所谓的虚拟机,甚至可以忽略不计。但是,总是有那么一批老古董,总会强迫假想自己写的程序会运行于各种资源非常苛刻的环境下,内心就是没法原谅shared_ptr所带来的极细微的损失。好比区区在下,每一次一用到shared_ptr,心里的那种负罪感啊,又多了几条废指令,又浪费多了十几个的堆字节,是否将生成内存碎片啊。终于有一刻顶不住了啦,去你妈的shared_ptr,老子不过想让你老老实实的代理内存资源,本本分分地做好你的分内之事,不劳你费心照顾其他的系统资源对象,那些场合本座自然有更好的解决方式。于是,制造轮子的悲剧又再次诞生了,虽然,他们一直在内心深处抵制新轮子的愚蠢行为,但是,……,只能说,知我者谓我心忧,不知我者谓我何求。
    每次想到shared_ptr要new一个_Ref_count_base的对象来管理计数,有人就恨得牙根发痒,巴不得把_Ref_count_base的数据成员搬过来,放之于那个要管理的对象的身上,以减少一小块内存。假如,客户传到shared_ptr构造函数的指针,此指针所指的内存,能再多几个字节(一个字节也行,最大值255,已足矣),以供我等存放一个long型的计数器,那就好办了。白痴也知道,这是不可能的事情。除非,此对象由shared_ptr来构造,那么还有办法再放多点额外内存进去。此话怎讲?大家都知道,C++中, new一个对象时,即意味着两步操作:1、分配一块内存;2、在此块内存上执行对象的构造函数。如果第1步的分配内存,能作多点手脚,比如说,分配一块比对象本身所占空间还要大的内存,那么我们的shared_ptr就可以把计数器放进对象之中了,也无须再new一个新的_Ref_count_base对象来管理计数器了。两块内存,合二为一,双剑合璧,妙哉妙哉。但,这如何做到呢?
    以下,是一个类从简单到复杂的物种进化历程。C++中,只要是通用类,即使再简单的需求,要写得可以被普遍承认,可以高高兴兴地到处使用,都绝非易事。而且,更悲剧的是,辛辛苦苦,呕心沥血造出来的轮子,还很有可能一问世就直接被枪毙,就算能苟且活下来,也不会有车愿意组装这一个废轮子。
    废话不说,书接上文,很明显,对象的new操作应该由我们的shared_ptr来掌控。任由用户来new,就太迟了,对象的内存块已经确定下来了,没文章可做啦。换句话说,shared_ptr必须模拟标准的new的两在操作分配内存和调用构造函数。由此可知,以下所探讨的shared_ptr运用场合也很有限,只适合于那些能看到构造函数并且知道其大小的C++类,所以,请大伙儿不要抱怨。唯其需求简单明确,所以才能高效。
首先,用一结构体__SharedObject来包含计数器和对象,如下所示:
struct  __SharedObject
{
    
void *  Object()     //  返回对象的地址,由于不知对象的类型,所以只能是void*,表示内存地址
    { return   this + 1 ; }
   
long  Incref() {  return  InterlockedIncrement( & m_nRef); }
    
long  Decref() {  return  InterlockedDecrement( & m_nRef); }
   
long  m_nRef;
};
    是否很简陋,本座没法也不想把它整得更加好看了。
    我们的shared_ptr,就暂时叫TShared_Ptr好了,其包含的数据成员,暂时很简单。就只有一个__SharedObject的指针而已,后面由于多继承多态的原因,将被迫多增加一个指针。
    好了,先看看TShared_Ptr的使用之道,此乃class template。由于共享对象由TShared_Ptr所造,所以,在其诞生之前,首先势必定义一TShared_Ptr变量,好比,TShared_Ptr<int> pInt;考虑TShared_Ptr的构造函数,如果在里面就急急忙忙给共享对象分配内存,将没法表达空指针这个概念,所以它的无参构造函数只是简单地将m_pShared置为NULL。然后,TShared_Ptr必须提供分配内存并执行构造函数的操作,叫Construct吧;然后,析构函数也绝不含糊,执行对象的析构函数并释放内存。于是,TShared_Ptr的基本代码就出炉了。
template < typename _Ty >
class  TShared_Ptr
{
public :
    TShared_Ptr() {m_pShared 
=  NULL; }
    TShared_Ptr(
const  TShared_Ptr &  _Other)
    {    
        
if  (m_pShared  !=  NULL)
        {
            m_pShared 
=  const_cast < __SharedObject *> (_Other.m_pShared);
            m_pShared
-> Incref();
        }
        
else
            m_pShared 
=  NULL;
    }

    
~ TShared_Ptr()
    {
        
if  (m_pShared  !=  NULL)
        {
            
if  (m_pShared -> Decref()  <=   0 )
            {
                
if  (m_pShared -> m_nRef  ==   0 )
                    DestroyPtr(
get ());
                free(m_pShared);
            }
        }
    }
    _Ty
&   operator * ()  const  _THROW0() { return   * get (); }
    _Ty 
* operator -> ()  const  _THROW0(){ return  ( get ());}

    
void  Construct()
    {
        ::
new  (m_pShared -> Object()) _Ty();     //  调用构造函数
        m_pShared -> Incref();     //  构造函数抛出异常,这一行将不执行
    }
    void  alloc()     //  假设malloc总能成功
    {
        m_pShared 
=  static_cast < __SharedObject *> (malloc( sizeof (_Ty) + sizeof (__SharedObject)));
        m_pShared
-> m_nRef  =   0 ;
    }

    _Ty 
* get ()  const  _THROW0() {    return  (_Ty * )(m_pShared -> Object());}
    __SharedObject
*  m_pShared;
};

可以写代码测试了,
    TShared_Ptr<int> pInt;
    pInt.Construct();
    (*pInt)++;
    TShared_Ptr<int> pInt1 = pInt;
    (*pInt1)++;
    咦,假如共享对象的构造函数带有参数,咋办呢?不要紧,重载多几个Construct就行了,全部都是template 成员函数。由于要实现参数的完美转发,又没有C++2011的move之助,我还在坚持C++98ISO,要写一大打呢,先示例几个,很痛苦,或者,可通过宏来让内心好受一点。
    template<typename T1>void Construct(const T1& t1);
    template<typename T1>void Construct(T1& t1);
    template<typename T1, typename T2>void Construct(const T1& t1, const T1& t2);
    template<typename T1, typename T2>void Construct(const T1& t1, T1& t2);
    template<typename T1, typename T2>void Construct(T1& t1, const T1& t2);
    template<typename T1, typename T2>void Construct(T1& t1, T1& t2);

    接下来就很清晰了,将shared_ptr的各种构造、赋值函数改写一遍就是了。然后,就可以高高兴兴地测试使用了。以上,是最理想环境下TShared_Ptr的很简单的实现,其操作接口多么的确简明扼要。
    开始,考虑各种变态环境,其实也不变态,完全很正当的需求。各种也不多,就两个而已:1、构造函数抛出异常,这个不是问题,由于TShared_Ptr的构造函数不抛出异常,其析构函数将被执行,检查到计数器为0,所以不调用共享对象的析构函数;
    2、多继承,这个有点头痛了。先看看代码,假设 class D : public B1, public B2,B1、B2都非空类;然后,B2* pB2 = pD,可以保证,(void*)pB2的值肯定不等于pD,也即是(void*)pB2 != (void*)pD。个中原因,在下就不多说了。但是,TShared_Ptr完全没法模拟这种特性,假如,坚持这样用,设pD为TShared_Ptr<D> ; 然后TShared_Ptr<B2>=pD,后果将不堪设想。一切皆因TShared_Ptr只有一条指向共享对象的指针,它还须拥有指向共享对象的基类子对象的指针,为此,必须添加此指向子对象的指针m_ptr,为_Ty*类型。因此,TShared_Ptr将内含两个指针,大小就比普通指针大了一倍,无论如何,到此为止,不能让它增大了。此外,TShared_Ptr增加了m_ptr成员后,还带来一些别的好处,类型安全倒也罢了,关键是在VC中单步调试下,可清晰地看到共享对象的各种状态,原来没有m_ptr的时候,就只能闷声发大财。
template < typename _Ty >
class  TShared_Ptr
{
public :
    
    template
< typename _OtherTy >
    TShared_Ptr(
const  TShared_Ptr < _OtherTy >&  _Other)
    {    
        m_ptr 
=  _Other.m_ptr;     //  类型安全全靠它了
        m_pShared  =  const_cast < __SharedObject *> (_Other.m_pShared);
        
if  (m_ptr  !=  NULL)
            m_pShared
-> Incref();
    }
    
    __SharedObject
*  m_pShared;
    _Ty
*  m_ptr;
};
    本轮子自然不美观,使用也颇不方便,但胜在背地里做的勾当少,一切均在预料之中。好像多线程下,还有点问题,但那只是理论上的疑惑,实际运行中,该不会那么巧吧。
    咦,都什么年代,还在研究茴香豆的四种写法,在下也承认,确实没啥意义,但乐趣很无穷呢,我就是喜欢。珍惜生命,远离C++。对了,想要完整的代码吗,没那么容易

你可能感兴趣的:(轻量级共享对象的灵巧指针的实现)