CComPtr
CComPtr派生自模板类CComPtrBase<T>,T代表某个COM接口。CComPtrBase<T>类只有一个成员变量T* p。下面是它们的代码分析:
构造函数
protected:
CComPtrBase() throw()
{
p = NULL;
}
CComPtrBase(_In_ int nNull) throw()
{
ATLASSERT(nNull == 0);
(void)nNull;
p = NULL;
}
CComPtrBase(_In_opt_ T* lp) throw()
{
p = lp;
if (p != NULL)
p->AddRef();
}
构造函数都被限定为protected,这样保证了无法直接创建CComPtrBase<T>对象,必须有子类的构造函数来调用它们。默认构 造函数和整数0作为参数的构造函数将p初始化为NULL。第三种形式会保存外部接口指针给p,并调用AddRef。
CComPtrBase没有提供AddRef成员函数供外部使用,为了生命周期一致,这是必要的。所以也不要通过operator->来调用内部p的AddRef方法。
CComPtr<T>增加了拷贝构造函数:
CComPtr(_In_ const CComPtr<T>& lp) throw() :
CComPtrBase<T>(lp.p)
{
}
本质上还是调用基类的第三种构造函数,因此也会调用p->AddRef方法。
析构函数
public:
~CComPtrBase() throw()
{
if (p)
p->Release();
}
注意析构函数没有virtual关键字,按照我们通常的认识,一个类如果被设计成被别的类继承,它需要一个虚析构函数,但是这里没有。为什么呢?因为它的子类CComPtr<T>没有任何成员变量,因此当我们如下调用的时候,并不会造成内存泄露。
CComPtrBase<IUnknown>* p=new CComPtr<IUnknown>();
delete p;
但是,这并不是一个很好的方法,虽然看似这里省了点虚函数调用的开销,但是谁又能保证不会有哪个人突然自己创造了一个 CComPtrBase<T>的子类,然后加了很多自己的成员变量,而如果他没有看过CComPtrBase<T>的代码,结果 会怎么样?我认为这的确是个设计不良!
析构函数保证了退栈的时候,如果p不为NULL,则会被调用接口的Release函数。CComPtr<T>没有提供析构函数,由于没有成员变量拥有析构函数,父类也没有虚析构函数,所以编译器也不会生成默认析构函数。
Release
作为一个原则,要记住的是我们总应该调用CComPtrBase::Release,而不是通过operator ->获得的接口指针来调用Release方法。为什么呢?再看看上面的代码,析构函数在p不为NULL不为的时候,会调用Release。如果我们 直接通过->来调用Release,我们又忘记将成员变量p设置为NULL设置为的话,那么析构函数会进行第二次Release调用,结果当然会导 致错误。下面是可靠地CComPtrBase::Release方法的代码:
void Release() throw()
{
T* pTemp = p;
if (pTemp)
{
p = NULL;
pTemp-