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