Как лучше инициализировать счетчик ссылок для не создаваемого COM-объекта? - PullRequest
4 голосов
/ 15 января 2010

У меня есть интерфейс 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(). Поскольку я полностью контролирую и CCreatorInterfaceImpl, и CObjectToCreateImpl, я могу пойти любым путем.

IMO, первый вариант более понятен - все вещи для подсчета ссылок находятся в одном куске кода. Я что-то наблюдал? Почему второй подход может быть лучше? Что из вышеперечисленного лучше и почему?

Ответы [ 3 ]

3 голосов
/ 15 января 2010

Обе вариации нарушают очень фундаментальный принцип COM

  • Никогда не вызывайте никакие методы, кроме AddRef, для COM-объекта с нулевым счетчиком ссылок.

В противном случае возможны всевозможные ошибки. Проще говоря, потому что это мешает людям совершать совершенно законные операции над объектом. Как положить их в умный указатель. Интеллектуальный указатель вызовет 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; 
}
2 голосов
/ 15 января 2010

Раймонд Чен написал соответствующую статью в своем блоге: На объектах с нулевым счетчиком ссылок

0 голосов
/ 15 января 2010

Я всегда использую следующий сценарий кода для создания возвращаемых объектов 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
 }
...