huaxiazhihuo

 

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

    毫无疑问,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++。对了,想要完整的代码吗,没那么容易

posted on 2012-10-30 15:39 华夏之火 阅读(1703) 评论(5)  编辑 收藏 引用 所属分类: c++技术探讨

评论

# re: 轻量级共享对象的灵巧指针的实现[未登录] 2012-10-31 13:07

1.“还能顽强地生存于各种恶劣的环境,好比多线程、引用循环”
shared_ptr解决循环引用了?! "_Ref_count_base"看似基于引用计数实现

2.“做了很多不为人知的勾当”
个人认为最好具体详细分析一下做了哪些“不为人知的勾当”,这些“勾当”有哪些缺点,是否还有其他优点,个人觉得你只这么泛泛的一说,不够专业,也不具有说服力,没法体现你的实现品真正能带来哪些好处。

3.对象计数管理一定要有一块内存来记录被指对象的引用次数,根据这块内存在哪,设计分为侵入式和非侵入式,各有不同特点。

4.认为拷贝构造函数中 if (m_pShared != NULL) 无意义,初值是一个随机值,此判断99%为true,是我看错了还是你写错了,析构貌似也不够严谨

5.“实际运行中,该不会那么巧吧。” 巧与不巧不是你能决定的  回复  更多评论   

# re: 轻量级共享对象的灵巧指针的实现 2012-10-31 13:40 华夏之火

@无
2、在下觉得shared_ptr背后做的事情太多了,超出了它的原本要求。3、侵入式的理解,应该是针对是否影响了类的代码而言,与内存在哪应该无关,好比在下的这个实现,就是非侵入式。4、拷贝构造函数中确实笔误了,可参考后文改进版,析构也有改进,只是没写出来。5、开玩笑而已,感觉应该没问题的,在下暂时还看不出来,你帮忙分析分析  回复  更多评论   

# re: 轻量级共享对象的灵巧指针的实现 2012-10-31 18:31 罗朝辉

这个实现还有一些值得考究的地方。比如:

1,如下可以编译通过么?
TShared_Ptr<int> pInt;
if (!pInt) {
}

if(pInt == 0) {
}

2,TShared_Ptr<int> 与 int * 的可置换性考虑没?
void normalize(int * pt);
TShared_Ptr<int> pInt;
normalize(pInt); //可否?

3,考虑了赋值构造操作符没?

4,智能指针若不能直接从原生指针构造,那还算智能么?
int a = 0;
TShared_Ptr<int> pInt = new TShared_Ptr<int>(&a);

  回复  更多评论   

# re: 轻量级共享对象的灵巧指针的实现 2012-10-31 23:45 华夏之火

@罗朝辉
本灵巧指针只为构造共享对象,自产自毁,从不考虑承接外界的原生指针。提供思路而已,至于具体语法形式的考究,在下也不大感兴趣。  回复  更多评论   

# re: 轻量级共享对象的灵巧指针的实现 2012-11-01 09:00 罗朝辉

@华夏之火

那些仅是语法形式的考究?那您自娱自乐吧  回复  更多评论   


只有注册用户登录后才能发表评论。
网站导航: 博客园   IT新闻   BlogJava   知识库   博问   管理


导航

统计

常用链接

留言簿(6)

随笔分类

随笔档案

搜索

积分与排名

最新评论

阅读排行榜

评论排行榜