¿Cómo inicializar mejor un contador de referencia para un objeto COM no creable?

Tengo una interfaz COM con un método que devuelve un objeto:

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

La clave es que llamar a ICreatorInterface::CreateObject() es la única forma de recuperar un objeto que implementa la interfaz IObjectToCreate .

En C ++ podría hacerlo de esta manera:

  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; } 

o de esta manera

  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; } 

La diferencia es cómo se inicializa el contador de referencia y cómo se implementa la eliminación del objeto en el caso de que QueryInterface() falle. Ya que controlo completamente tanto CCreatorInterfaceImpl como CObjectToCreateImpl puedo ir de una de las dos maneras.

En mi opinión, la primera variante es más clara: todas las cosas relacionadas con el recuento de referencias están en una pieza de código. ¿He supervisado algo? ¿Por qué podría ser mejor el segundo enfoque? ¿Cuál de las anteriores es mejor y por qué?

Ambas variaciones violan un principio muy fundamental de COM

  • Nunca llame a ningún método, que no sea AddRef, en un objeto COM que tenga un recuento de ref de cero.

Hacer lo contrario lleva a todo tipo de errores. En pocas palabras, porque impide que las personas realicen operaciones completamente legales sobre el objeto. Como ponerlos en un puntero inteligente. El puntero inteligente llamaría a AddRef, pondría el conteo a 1, y más tarde, soltando el conteo a 0 y provocando que el objeto se autodestruya.

Sí, me doy cuenta de que el 90% de las implementaciones de QueryInterface no hacen esto. Pero también te garantizo que hay algunos por ahí que sí 🙂

Creo que el enfoque más simple es llamar a AddRef inmediatamente después de crear el objeto. Esto permite que el objeto se comporte como un objeto COM normal en el momento más temprano posible.

Me he encontrado con este problema en el pasado y he escrito un pequeño método de ayuda (asumiendo que el objeto está implementado en ATL).

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

Raymond Chen escribió un artículo relevante en su blog: Sobre objetos con un recuento de referencia de cero.

Siempre uso el siguiente escenario de código para crear objetos com devueltos para evitar problemas con la memoria. Por supuesto, esto funciona porque mis objetos son referencias contabilizadas = 0 cuando se crean. Esto siempre me parece más claro que tratar de manejar la condición de error con el uso del operador de eliminación.

  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 }