在标题中,包含以下代码。
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 *&'
我知道如何使用模板或宏编写此函数。但我想知道为什么会这样。
答案 0 :(得分:2)
注意:要完全掌握本文中的信息,需要了解lvalues and rvalues的一些知识。
SafeRelease(_D3DDevice); // ill-formed
此处我们尝试将_D3DDevice
的地址传递给 SetRelease ,但它不可调用,因为它需要 lvalue (引用)类型指针到的IUnknown
仅仅因为 Derived 继承自 Base ,并不意味着可以转换 Derived 的左值指针指向 Base 的左值类型指针。
从Derived*
到Base*
的隐式转换将产生右值。
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)将产生一个临时类型指向IDirect3DDevice9 的指针,该指针可以隐含地变成指向IUnknown 的指针,因为 IDirect3DDevice9 < / em>继承自 IUnknown 。
我们不再尝试将左值引用形成隐式yield指针,代码编译..
...但是,这也意味着我们将无法更新传入的任何指针参数的值,因此如果这是一个要求,您应该/应该使用模板,以便您可以获得对实际值作为参数传递。
template<typename T>
inline void SafeRelease(T * & in_COM_Pointer);
答案 1 :(得分:2)
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;
}
}
答案 2 :(得分:1)
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
答案 3 :(得分:1)
“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
- 实际与正式论证的关系,它通常被称为Liskov替代原则,LSP,在Barbara Liskov之后。
“2。如何正确编写?
一般问题的一个解决方案,如Hans Passant has already mentioned,就是使用模板来直接处理手头的类型或类型,而不是转换。
在这个具体案例中,只要您确定没有nullpointer,只需拨打p->Release()
而不是SafeRelease( p )
。
“3。如果继承不能用于编写此函数,我应该使用模板吗?
你可以使用模板,但没有必要;见上文。
“4。使用建议的方法实施时我需要注意什么?
建议的方法涉及为COM接口指针设想的隐式转换Derived*
→Base*
。
请注意,虽然Derived*
→Base*
转换通常也适用于COM接口,但IUnknown
接口受特殊规则的约束。
即,COM对象内部可能有多个IUnknown
子对象,对应于此对象的可能IUnknown*
指针,但这些指针值只有一个标识对象
因此,当您需要一个标识对象的IUnknown
指针时,可以与其他IUnknown
指针进行比较的指针来检查它是否是同一个对象,您必须使用QueryInterface
获取IUnknown
指针。
很高兴您可以通过任何接口指针使用QueryInterface
,并且由于此成员函数是通过所有其他接口继承的IUnknown
接口提供的,因此它说明您可以使用非标识IUnknown
指针用于除识别之外的其他目的。