Функция освобождения COM-объекта выдает ошибку при передаче параметра IUnknown * & as

В заголовок включен следующий код.

inline void SafeRelease( IUnknown * & in_COM_Pointer )
    {
        if ( NULL != in_COM_Pointer )
        {
            in_COM_Pointer->Release();
            in_COM_Pointer = NULL;
        }
    }

Когда он используется, как указано ниже,

SafeRelease(_D3DDevice); // _D3DDevice is a "IDirect3DDevice9 *". According to documentation it is inherited from "IUnknown".

Выдает ошибку компиляции:

ошибка C2664: «SafeRelease»: невозможно преобразовать параметр 1 из «IDirect3DDevice9 *» в «IUnknown *&»

Я знаю, как написать эту функцию, используя шаблоны или макросы. Но я хочу знать, почему это происходит.

  1. Почему это дает ошибку?
  2. Как его правильно написать?
  3. Если для написания этой функции нельзя использовать наследование, следует ли использовать шаблоны?
  4. Что-нибудь, с чем я должен был бы быть осторожен при реализации с использованием предложенного метода?

person Deamonpog    schedule 14.06.2014    source источник
comment
Поскольку один и тот же ответ повторяется (несколько раз) ниже, я опускаю свой и просто прошу вас прочитать этот документ. Есть причины не делать то, что вы пытаетесь, и вместо того, чтобы прокторировать еще один образец лучшего способа сделать то, что вы не должны делать в первую очередь, вы можете рассмотреть альтернативы. Сам шаблон, который вы пытаетесь использовать, описан в связанной статье с предостережениями и всем остальным.   -  person WhozCraig    schedule 14.06.2014
comment
Спасибо за хорошую ссылку :). На самом деле я пытаюсь измерить разрыв в производительности между интеллектуальными указателями и этой реализацией для небольшого графического моделирования. Кстати, в статье предлагается передавать указатель на указатель, в то время как они могут передавать указатель по ссылке. Это плохо, не так ли.   -  person Deamonpog    schedule 14.06.2014
comment
Если вы имеете в виду реализацию SafeRelease в статье, в конечном итоге это предполагает, что вы просто не делаете этого вообще, если вы пишете код на C ++ (а вы это делаете). К моменту завершения встраивания я был бы удивлен, если бы шаблон SafeRelease имел какое-либо преимущество над интеллектуальными указателями, не нарушая фундаментальные правила COM.   -  person WhozCraig    schedule 14.06.2014


Ответы (4)


” 1. Почему возникает ошибка?

Учти это:

struct Animal {};
struct Dog: Animal { void bark() {} };
struct Dolphin: Animal { void dive() {} };

void foo( Animal*& p ) { p = new Dolphin(); }

auto main() -> int
{
    Dog* p = new Dog();
    foo( p );             //! C2664, Would have changed p to point to Dolphin.
    p->bark();            // Uh huh...
}

Итак, это не разрешено.

Есть еще одно и то же, например. относительно глубокой const-ности фактического и формального аргумента, и он широко известен как принцип подстановки Лисков, LSP, в честь Барбары Лисков.


” 2. Как правильно написать?

Одним из решений общей проблемы, как уже упоминал Ханс Пассант, является использование шаблонов для прямой работы с типом или типы под рукой, без преобразования.

Однако в этом конкретном случае, если вы уверены, что у вас нет нулевого указателя, просто вызовите p->Release() вместо SafeRelease( p ).


” 3. Если для написания этой функции нельзя использовать наследование, должен ли я использовать шаблоны?

Вы можете использовать шаблоны, но это не обязательно; см. выше.


» 4. Есть ли что-то, с чем мне нужно быть осторожным при реализации этого с использованием предложенного метода?

Предлагаемый метод включает предполагаемое неявное преобразование Derived*Base* для указателей интерфейса COM.

Обратите внимание, что хотя преобразование Derived*Base* обычно хорошо работает и с COM-интерфейсами, интерфейс IUnknown подчиняется очень особым правилам.

А именно, внутри COM-объекта может быть несколько IUnknown подобъектов, соответствующих возможным IUnknown* указателям на этот объект, но только одно из этих значений указателя идентифицирует объект.

Поэтому, если вам нужен указатель IUnknown, который идентифицирует объект, указатель, который можно сравнить с другими указателями IUnknown, чтобы проверить, является ли это одним и тем же объектом, вы должны использовать QueryInterface для получения указателя IUnknown.

К счастью, вы можете использовать QueryInterface с помощью любого имеющегося у вас указателя интерфейса, а поскольку эта функция-член предоставляется через интерфейс IUnknown, который наследуют все остальные интерфейсы, это показывает, что вы можете использовать неидентифицирующие указатели IUnknown для других целей, кроме идентификации.

person Cheers and hth. - Alf    schedule 14.06.2014

COM не разрешает приведение указателей интерфейса, вы должны использовать QueryInterface(). Это обеспечивается компилятором C++. Как это:

class Base {};
class Derived : /*public*/ Base {};

inline void SafeRelease(Base* & ptr) {}

void test() {
    auto p = new Derived;
    SafeRelease(p);      // C2664
}

Вы можете сделать это с помощью функции шаблона:

template<typename T>
inline void SafeRelease(T * & in_COM_Pointer) {
    if (NULL != in_COM_Pointer) {
        in_COM_Pointer->Release();
        in_COM_Pointer = NULL;
    }
}
person Hans Passant    schedule 14.06.2014
comment
+1 было бы еще лучше с объяснением того, как, если бы вызов OP был разрешен, он позволил бы сохранить указатель на другой несвязанный тип в фактической переменной аргумента (возможно, с упоминанием LSP). - person Cheers and hth. - Alf; 14.06.2014

Примечание. Чтобы полностью понять информацию в этом посте, необходимо знать некоторые сведения о lvalues ​​и rvalues. требуется.


Вступление

SafeRelease(_D3DDevice); // ill-formed

Здесь мы пытаемся передать адрес _D3DDevice в SetRelease, но его нельзя вызвать, так как для него требуется lvalue (ссылка на) типа указатель-на- IНеизвестно.

Тот факт, что Derived наследуется от Base, не означает, что указатель lvalue на Derived может быть преобразован в < strong>lvalue типа указателя на Base.

Неявное преобразование из Derived* в Base* даст rvalue.

struct A     { };
struct B : A { };

void func (A*&);

B* p = ...;
func (ptr);      // ill-formed, the implicitly yield `A*` is not an lvalue,
                 //             and rvalues cannot bind to lvalues refernces

Что такое "решение"?

inline void SafeRelease( IUnknown * in_COM_Pointer );
SafeRelease (&_D3DDevice); // (A)

(A) will yield a temporary of type pointer-to-IDirect3DDevice9, this pointer can implicitly turn into a pointer-to-IUnknown since IDirect3DDevice9 inherits from IUnknown.

Мы больше не пытаемся сформировать lvalue-ссылку на неявный указатель yield, и код компилируется.


Подразумеваемое

... но это также означает, что мы не сможем обновить значение любого переданного аргумента указателя, поэтому, если это требование, у вас есть/должно прибегать к использованию шаблонов, чтобы вы могли получить ссылку на переданное фактическое значение как аргумент.

template<typename T>
inline void SafeRelease(T * & in_COM_Pointer);
person Filip Roséen - refp    schedule 14.06.2014
comment
С этим техническим решением цель SafeRelease, обнулить переменную-указатель аргумента, больше не выполняется: функция стала ~бессмысленной. - person Cheers and hth. - Alf; 14.06.2014
comment
@Cheersandhth.-Alf Я думал, что было ясно, что такая цель плохо сформулирована с нынешним подходом, учитывая то, что написано в посте. Изменить: см. обновленный пост для уточнения. - person Filip Roséen - refp; 14.06.2014
comment
Спасибо за ответ. Вы имели в виду IUnknown * * in_COM_Pointer ? - person Deamonpog; 14.06.2014
comment
&_D3DDevice формирует указатель на указатель. Цитируя вопрос, _D3DDevice — это IDirect3DDevice9 *. Также, как уже упоминалось, попытка решения в этом ответе выбрасывает ребенка вместе с водой из ванны (независимо от того, что можно по праву считать этого конкретного ребенка отрицательной ценностью). - person Cheers and hth. - Alf; 14.06.2014
comment
@Cheersandhth.-Пост Альфа исправлен, спасибо, что сообщили мне. Как указано в вопросе, решение возможности вызова функции подразумевает невозможность изменить аргумент, с которым вызывается функция. Описание есть, так как OP явно попросил об этом в своем вопросе. - person Filip Roséen - refp; 14.06.2014
comment
@FilipRoséen-refp: Посмотрите на ответ Ханса Пассана или мой ответ о том, как включить вызов, сохраняя при этом цель. Не то чтобы я согласен с этой целью, которая, ИМХО, в основном является негативным соглашением среды кодирования Microsoft, таким как венгерская нотация или макросы T. Но без этой цели вообще не имеет смысла иметь эту функцию. - person Cheers and hth. - Alf; 14.06.2014
comment
@Cheersandhth.-Alf, такое объявление шаблона включено в мой пост и формулировка о том, почему это необходимо. - person Filip Roséen - refp; 14.06.2014
comment
ну теперь это так ;-) - person Cheers and hth. - Alf; 14.06.2014

IDirect3DDevice9 унаследовано от IUnknown, но это не совсем IUnknown. Это делает переменную IDirect3DDevice9* несовместимой с аргументом IUnknown*&.

Следовательно, вам нужно либо приводить между типами, либо использовать более гибкую функцию освобождения, основанную на шаблоне, например:

template <typename IFoo>
VOID SafeRelease(IFoo*& pFoo)
{
    if(!pFoo)
        return;
    pFoo->Release();
    pFoo = NULL;
}

IDirect3DDevice9* pDevice = ...
...
SafeRelease(pDevice);

Или, скорее, и это серьезное улучшение точности разработки, используйте обертки шаблонов вместо необработанных указателей интерфейса, таких как CComPtr.

CComPtr<IDirect3DDevice9> pDevice = ...
...
pDevice.Release(); // or pDevice = NULL, 
           // or nothing - automatic release on going out of scope
person Roman R.    schedule 14.06.2014