1

In a header, following code is included.

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

When It is used as given below,

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

It gives a compilation error:

error C2664: 'SafeRelease' : cannot convert parameter 1 from 'IDirect3DDevice9 *' to 'IUnknown *&'

I know how to write this function using templates or macros. But I want to know why this happens.

  1. Why does this give error?
  2. How to write it correctly?
  3. If inheritance cannot be used for writing this function should I use templates?
  4. Anything that I would have to be careful about when implementing it using suggested method?
Deamonpog
  • 805
  • 1
  • 10
  • 24
  • Since the same answer is replicated (several times) below, I'm dropping mine and simply asking that you [**read this document**](http://msdn.microsoft.com/en-us/library/windows/desktop/ff485839%28v=vs.85%29.aspx). There are reasons *not* to do what you're trying, and rather than proctor yet-another boilerplate of a better way to do something you shouldn't be doing in the first place, you may want to consider alternatives. The very pattern you're attempting is called out within the linked article, caveats and all. – WhozCraig Jun 14 '14 at 11:45
  • Thank you for the nice link :). I am actually trying to measure the performance gap between smart pointers and this implementation for a small graphical simulation. BTW the article suggests passing a pointer-to-pointer while they can pass a pointer-by-reference. That is bad, isn't it. – Deamonpog Jun 14 '14 at 12:52
  • If you're referring to the article's implementation of `SafeRelease`, it ultimately suggests you simply don't do it at all if you're coding in C++ (and you are). By the time inlining is finished I'd be surprised if the `SafeRelease` pattern has *any* edge on smart pointers without violating fundamental COM rules. – WhozCraig Jun 14 '14 at 12:57

4 Answers4

2

Note: To fully grasp the information in this post, some knowledge about lvalues and rvalues is required.


Introduction

SafeRelease(_D3DDevice); // ill-formed

Here we try pass the address of a _D3DDevice to SetRelease, but it is not callable since it requires an lvalue (a reference to) of type pointer-to-IUnknown.

Just because Derived inherits from Base, does not mean that an lvalue pointer to Derived can be converted to an lvalue of type pointer to Base.

The implicitly conversion from Derived* to Base* will yield an 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

What is the "solution"?

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.

We no longer try to form an lvalue-reference to the implicitly yield pointer, and the code compiles..


Implications

... but, this also means that we will be unable to update the value of any pointer argument passed in, so if that is an requirement you have/should resort to using templates so that you can get a reference to the actual value passed as argument.

template<typename T>
inline void SafeRelease(T * & in_COM_Pointer);
Community
  • 1
  • 1
Filip Roséen - refp
  • 62,493
  • 20
  • 150
  • 196
  • With this technical solution the purpose of `SafeRelease`, of nulling the argument pointer variable, is no longer met: the function has become ~meaningless. – Cheers and hth. - Alf Jun 14 '14 at 11:24
  • @Cheersandhth.-Alf I thought it was clear that such a "purpose" is ill-formed with the current approach, given what is written in the post. **Edit:** see updated post for clearification. – Filip Roséen - refp Jun 14 '14 at 11:32
  • Thank you for answering. Did you mean `IUnknown * * in_COM_Pointer` ? – Deamonpog Jun 14 '14 at 11:34
  • The `&_D3DDevice` forms a pointer to pointer. Quoting the question, "_D3DDevice is a "IDirect3DDevice9 *". Also as already mentioned, the attempted solution in this answer throws out the baby with the bathwater (no matter that one may rightfully consider this particular baby to be of negative value). – Cheers and hth. - Alf Jun 14 '14 at 11:43
  • @Cheersandhth.-Alf post fixed, thanks for letting me know. As stated in the question the *"solution"* of being able to call the function has the impliciation of not being able to modify the argument that the function is being invoked with. The description is there since OP explicitly asked for one in his question. – Filip Roséen - refp Jun 14 '14 at 11:48
  • @FilipRoséen-refp: Look at Hans Passant's answer or my answer for how to enable calling while keeping the purpose. Not that I agree with that purpose, which IMHO is mainly a negative utility Microsoft coding environment convention, like Hungarian notation or `T` macros. But without that purpose it doesn't make much sense to have the function at all. – Cheers and hth. - Alf Jun 14 '14 at 11:58
  • @Cheersandhth.-Alf such a template declaration is included in my post, and wording about why that is neccessary. – Filip Roséen - refp Jun 14 '14 at 12:02
  • well *now* it does ;-) – Cheers and hth. - Alf Jun 14 '14 at 12:12
2

COM does not permit casting interface pointers, you must use QueryInterface(). This is enforced by the C++ compiler. Like this:

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

inline void SafeRelease(Base* & ptr) {}

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

You can do it with a template function:

template<typename T>
inline void SafeRelease(T * & in_COM_Pointer) {
    if (NULL != in_COM_Pointer) {
        in_COM_Pointer->Release();
        in_COM_Pointer = NULL;
    }
}
Hans Passant
  • 922,412
  • 146
  • 1,693
  • 2,536
  • +1 would be even better with explanation of how if the OP's call was permitted it would enable storing a pointer to different unrelated type in the actual argument variable (maybe with mention of LSP). – Cheers and hth. - Alf Jun 14 '14 at 11:26
1

IDirect3DDevice9 is inherited from IUnknown but it is not exactly IUnknown. This makes IDirect3DDevice9* variable incompatible with IUnknown*& argument.

Hence, you need to either cast between types, or use a more flexible releasing function, template based, e.g.:

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

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

Or rather, and it's serious improvement in development accuracy, use template wrappers over raw interface pointers, such as CComPtr.

CComPtr<IDirect3DDevice9> pDevice = ...
...
pDevice.Release(); // or pDevice = NULL, 
           // or nothing - automatic release on going out of scope
Roman R.
  • 68,205
  • 6
  • 94
  • 158
1

” 1. Why does this give error?

Consider this:

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

So, this is not permitted.

There’s more of the same, e.g. regarding deep const-ness of actual versus formal argument, and it’s generally known as the Liskov Substitution Principle, the LSP, after Barbara Liskov.


” 2. How to write it correctly?

One solution for the general problem, as Hans Passant has already mentioned, is to use templating in order to deal directly with the type or types at hand, no conversion.

In this concrete case, however, as long as you’re sure that you don’t have a nullpointer, just call p->Release() instead of SafeRelease( p ).


” 3. If inheritance cannot be used for writing this function should I use templates?

You can use templating but it’s not necessary; see above.


” 4. Anything that I would have to be careful about when implementing it using suggested method?

The suggested method involves an envisioned implicit conversion Derived*Base* for COM interface pointers.

Do note that while Derived*Base* conversion generally works nicely also with COM interfaces, the IUnknown interface is subject to very special rules.

Namely, internally a COM object may have multiple IUnknown sub-objects, corresponding to possible IUnknown* pointers into this object, but only one of these pointer values identifies the object.

So when you want an IUnknown pointer that identifies the object, a pointer that can be compared to other IUnknown pointers to check if it’s the same object, you have to use QueryInterface to obtain the IUnknown pointer.

Happily you can use QueryInterface via any interface pointer that you have, and since this member function is provided via the IUnknown interface that all other interfaces inherit, it illustrates that you can use non-identifying IUnknown pointers for other purposes than identification.

Community
  • 1
  • 1
Cheers and hth. - Alf
  • 142,714
  • 15
  • 209
  • 331