如何更好地初始化非可创建COM对象的引用计数器?

4

我有一个COM接口,其中包含一个返回对象的方法:

interface ICreatorInterface {
    HRESULT CreateObject( IObjectToCreate** );
};

关键在于调用 ICreatorInterface::CreateObject() 是检索实现 IObjectToCreate 接口的对象的唯一方式。

在 C++ 中,我可以这样做:

 HRESULT CCreatorInterfaceImpl::CreateObject( IObjectToCreate** result )
 {
     //CObjectToCreateImpl constructor sets reference count to 0
     CObjectToCreateImpl* newObject = new CObjectToCreateImpl();
     HRESULT hr = newObject->QueryInterface( __uuidof(IObjectToCreate), (void**)result );
     if( FAILED(hr) ) {
         delete newObject;
     }
     return hr;
 }

或者这样

 HRESULT CCreatorInterfaceImpl::CreateObject( IObjectToCreate** result )
 {
     //CObjectToCreateImpl constructor sets reference count to 1
     CObjectToCreateImpl* newObject = new CObjectToCreateImpl();
     HRESULT hr = newObject->QueryInterface( __uuidof(IObjectToCreate), (void**)result );
     // if QI() failed reference count is still 1 so this will delete the object
     newObject->Release();
     return hr;
 }

不同之处在于引用计数器的初始化方式以及在QueryInterface()失败的情况下对象删除的实现方式。由于我完全控制CCreatorInterfaceImplCObjectToCreateImpl,所以我可以选择其中任何一种方式。

在我看来,第一种变体更清晰 - 所有引用计数相关的内容都在一个代码块中。我有没有忽略什么?为什么第二种方法可能更好?上述哪种方法更好,为什么?

3个回答

3

两种变化都违反了COM的一个非常基本的原则:

  • 不要在引用计数为零的COM对象上调用除AddRef之外的任何方法。

否则会导致各种各样的错误。简单来说,因为它阻止了人们对对象进行完全合法的操作,例如将它们放入智能指针中。智能指针会调用AddRef,将计数设置为1,稍后调用Release将计数设置为0,导致对象自我销毁。

是的,我知道90%的QueryInterface实现不会这样做。但我也保证有一些会这样做的 :)

我认为最简单的方法是在创建对象后立即调用AddRef。这使对象尽可能早地像普通的COM对象一样运行。

我以前遇到过这个问题,我写了一个不错的小助手方法(假设对象是在ATL中实现的)。

template <class T>
static 
HRESULT CreateWithRef(T** ppObject)
{
    CComObject<T> *pObject;
    HRESULT hr = CComObject<T>::CreateInstance(&pObject);
    if ( SUCCEEDED(hr) )
    {
        pObject->AddRef();
        *ppObject = pObject;
    }

    return hr; 
}

我理解你的观点,但是同样的行为可以更简单地实现:在构造函数中将refcount初始化为0,并在相关代码中使用智能指针 - 只需将new出来的对象绑定到智能指针上,然后调用QI() - ATL::CComPtr允许这样做,也许_com_ptr_t也可以。 - sharptooth

2

0
我通常使用以下代码方案创建返回的 COM 对象,以避免内存问题。当然,这是因为我的对象在创建时引用计数为 0。对我来说,这似乎总是比尝试使用删除运算符处理错误条件更清晰明了。
 HRESULT CCreatorInterfaceImpl::CreateObject( IObjectToCreate** result )
 {
     //CObjectToCreateImpl constructor sets reference count to 0
     CObjectToCreateImpl* newObject = new CObjectToCreateImpl();

     newObject->AddRef();

     HRESULT hr = newObject->QueryInterface( __uuidof(IObjectToCreate), (void**)result);

     newObject->Release(); // release my addref, if QI succeeded it AddRef'd, if not the object is destroyed

     return hr; // return result from QI
 }

1
如果您使用像CComPtr这样的智能指针,可以用两倍少的代码实现相同的效果。 - sharptooth
真的,唯一的小问题是,如果代码需要调用对象上的某些非接口方法(例如内部初始化 - 由于潜在的错误情况,无法在构造函数中处理 - 或其他),您仍然需要指向正确类型的指针而不是接口。 - Ruddy
在大多数情况下,您可以使用实际的最终派生类型而不是接口来参数化CComPtr。 - sharptooth

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接