首先一个原则是这样:被用来delete的指针,一定是new出来的。
在设计智能指针的时候发现,如果采用delete this机制,到最后可能会有严重的问题。
假设有这样的多重继承:
class CA{};
class CB{};
class CC{};
class CABC : public CA, public CB, public CC
{};
CABC* pAbc = new CABC;
CA* pA = pAbc;
CB* pB = pAbc;
CC* pC = pAbc;
A B C指针的值都是不一样的,如果是简单的使用delete 来删除,问题就大条了,pB pC的地址和new出来的pAbc不一样,这样就出现堆错误。
如果设计入侵式RefObj问题就大条了:
class Ref
{
public:
virtual void Test()
{
std::cout << this << std::endl;
}
int m_i;
};
class CA : public virtual Ref{};
class CB : public virtual Ref{};
class CC : public virtual Ref{};
class CABC : public CA, public CB, public CC{};
C* pAbc = new CABC;
CA* pA = pAbc;
CB* pB = pAbc;
CC* pC = pAbc;
通过打印可以看到,虽然ABC 在基类Test打印出来的this地址一致,但是和实际正确的delete指针(new出来的)是不一致的,这就准备堆错误了,,,
这基本意味着使用入侵式样Ref的类,使用了多重继承就是作死。除非和com的思想一样,Ref不是作为基类处理,而是作为纯虚接口,但是又会使得编码复杂化,基本上全部要用组合来替代继承
class IXXX;
class CXXX;
class IYYY;
class CYYYXXX : public CXXX, public IYYY;
这样的用法无法实现,应为没有实现IXXX接口,是一个虚基类,实现了在调用的时候也是各种基类未指定的错误。
奇怪的是shared_ptr没有这个限制,猜测是在第一次从裸指针构造出来的时候,保留了原始地址,生成了一个析构函数,会在shared_ptr之间共享,直到最后一个对象析构的时候调用.
实际观察std::shared_ptr的实现,发现其构造函数不是控制一定要转换成T指定的类型,而是给入参数的实际类型,后面的管理也分两个部分,一层得到根据T转换后的值,实际的生命周期管理得到仍然是实际类型,所以他delete的时候还是当初给定的值。
所以,感觉智能指针不是什么好东西,背后的细节太多,一个不小心就死翘翘
class RefObj
{
public:
RefObj() : m_i(0){}
public:
void Increate(){m_i++;}
void Decreate()
{
if(0 == --m_i)
{
delete this;
}
}
private:
int m_i;
};
class BaseA : public virtual RefObj {public: int m_iA;};
class BaseB : public virtual RefObj {public: int m_iB;};
class BaseC : public virtual RefObj {public: int m_iC;};
class CBaseABC : public BaseA,
public BaseB,
public BaseC
{
};
void Test(BaseB* pB)
{
pB->Decreate();
}
int _tmain(int argc, _TCHAR* argv[])
{
ETcpSocket tcpSocket;
CBaseABC* pABC = new CBaseABC;
pABC->Increate();
BaseB* pB = pABC;
Test(pB);
return 0;
}
始终还是觉得相比shared_ptr,RefObj这种,还是有部分场景更加格式,至少不用担心类型转换变得异常麻烦,,,
后发现如下实现方式:
class IRefObj
{
public:
virtual void __DecRef() = 0;
};
template<typename TType>
class TRefObj : public IRefObj
{
public:
void __DecRef()
{
delete reinterpret_cast<TType*>(this);
}
};
class CRefTest : public TRefObj<CRefTest>
{
};
class CRefTestA : public CRefTest
{
};
class CTestA {int i;};
class CTestB {int j;};
class CTestC : public CTestB, public CRefTestA, public CTestA
{
};
int _tmain(int argc, _TCHAR* argv[])
{
CTestC* pTC = new CTestC;
CTestB* pTB = pTC;
CRefTestA* pRTA = pTC;
CTestA* pTA = pTC;
pRTA->__DecRef();
这个时候CRefTest是预期额值,怀疑可能和编译器有关,VS测试OK,gcc没去实验。
分析了一下原因:C++保证单根继承的时候基类和派生类地址是一样的,如果是多重继承,那么也保证和最深的根父类地址样,顺便的,从最深的根节点,选一路下来是安全的。
class CRefTest : public TRefObj<CRefTest>所以约定TRefObj<CRefTest>这玩意和永远和接口在一个级别,可以保证IRefObj永远是最深的根,就安全了?
目前只在控件设计的时候用RefObj吧,,,坑的比较深,,,