1

Having quite a few interfaces in my code, I wanted to encapsulate repeating Release code in another method and not in a macro because this is C++ and I hate using macros. My initial attempt was to write a method such as

void SafeRelease(IUnknown **ppInterface) {
    if(*ppInterface) {
        (*ppInterface)->Release();
        (*ppInterface) = nullptr;
    }
}

however applying this method to a IDirect3DSurface9 * e.g. like SafeRelease(&mySurface) yields the error IDirect3DSurface9 ** is incompatible with IUnknown **.

  1. What am I doing here wrong?
  2. Is there a better approach (hopefully not using macros) to implement such a function?
Christian Ivicevic
  • 10,071
  • 7
  • 39
  • 74

3 Answers3

1

Here's my method:

template <typename T> void SafeRelease(T*& ptr)
{
    if(ptr)
    {
        ptr->Release();
        ptr = nullptr;
    }
}

Example usage:

IDirect3DDevice9 *pD3DDevice = NULL;
d3d->CreateDevice(..., &pD3DDevice);
SafeRelease(pD3DDevice);

You may inline this function if you want.

Toribio
  • 3,963
  • 3
  • 34
  • 48
0

You could use a template:

template<class DXInterface>
void SafeRelease(DXInterface **ppInterface) {
    if(*ppInterface) {
        (*ppInterface)->Release();
        (*ppInterface) = nullptr;
    }
}

You could also use a std::unique_ptr or std::shared_ptr to automatically cleanup:

#include <memory>
#include <iostream>

struct Releaser {
   template<class DXInterface>
   void operator()(DXInterface *pInterface) const {
       if(pInterface) {
           pInterface->Release();
       }
   }
};

// For illustrative purposes only (supplied in DX9 headers)
struct IDirect3DSurface9 { void Release() { std::cout << "Released surface\n";} };
struct IDirect3DTexture9 { void Release() { std::cout << "Released texture\n";} };

void DX9CreateSurface( IDirect3DSurface9** surface ) 
{ 
    *surface = new IDirect3DSurface9();
}

void DX9CreateTexture( IDirect3DTexture9** texture ) 
{ 
    *texture = new IDirect3DTexture9();
}

// Your factory functions
IDirect3DSurface9* createSurface( /*init params go here*/ )
{
    IDirect3DSurface9* surface;
    DX9CreateSurface( &surface );
    return surface;
}

IDirect3DTexture9* createTexture( /*init params go here*/ )
{
    IDirect3DTexture9* texture;
    DX9CreateTexture( &texture );
    return texture;
}

int main()
{
  typedef std::unique_ptr<IDirect3DSurface9, Releaser> SurfacePtr;
  typedef std::unique_ptr<IDirect3DTexture9, Releaser> TexturePtr;

  SurfacePtr surface( createSurface() );
  TexturePtr texture( createTexture() );
  // ... use surface and texture here
  // Automatically released here when their lifetimes ends.
}

Note that they use the same Releaser, and note that a call to surface.reset() would also release the interface and would set the pointer inside the unique_ptr to null to boot. These two objects could be members of your class rather than objects in main().

metal
  • 6,202
  • 1
  • 34
  • 49
  • This way I would have to define this method for all the Direct3D interfaces I am using and which need to be released. This is the reason why I wanted to use `IUnknown`. – Christian Ivicevic Feb 04 '13 at 21:54
  • 1
    Better to make Releaser's operator() a template. – Puppy Feb 04 '13 at 22:02
  • Technically, the compiler would define them all for you, and it would almost certainly inline that function anyway, regardless of whether you make it a template. See also my update about using smart pointers. – metal Feb 04 '13 at 22:04
  • I am currently not using smart pointers and doing this therefore the old fashioned way. To create such an interface I rely on functions like `CreateOffscreenPlainSurface` which take a `IDirect3DSurface9 **` parameter marked as `_Out_`. How can I let the function access my smart pointer then, because it surely won't work just by passing a `unique_ptr`, will it? – Christian Ivicevic Feb 04 '13 at 22:08
  • If it's taking a double pointer in, then you would need to use a separate object (or your own little factory function) and then hand that over to the smart pointer to manage. If it's only using a single pointer, then you can call unique_ptr::get(). – metal Feb 04 '13 at 22:15
  • This whole workaround with factories and smart pointers is too much - I will stick with the template which actually works now. – Christian Ivicevic Feb 04 '13 at 22:21
  • As you wish. I added some factories to illustrate. Using smart pointers virtually eliminates resource leaks, which can build up over time if you do a lot of creating resources, as in a game. – metal Feb 04 '13 at 22:22
  • For your interest, the way Flávio Toribio has written his template seems much better with a `*&` rather than `**` as I do not have to reference the pointers once more! – Christian Ivicevic Feb 04 '13 at 22:25
  • My Releaser functor also does not have a double pointer *and* it supports RAII. Why program in C-style, especially when the C++ version is safer in the face of bugs and exceptions, easier to read and reason about, and free of run-time penalties with inlining enabled? Worry more about the game and less about the plumbing. – metal Feb 05 '13 at 14:46
0

What am I doing here wrong?

I just had the same question, also for COM SafeRelease. So here goes:

void SafeRelease(IUnknown **ppInterface) 
...
IDirect3DSurface9 * mySurface = new ...
...
SafeRelease(&mySurface);

IDirect3DSurface9 *, by virtue of inheritance, can be cast to IUnknown *. But, counterintuitively, IDirect3DSurface9 ** cannot be cast to IUnknown **. If it were allowed, then inside your SafeRelease(IUnknown**) you could do the following:

// obtain a pointer to an instance of some random subinterface of IUnknown
*ppInterface = pMyRamdomComInterfacePointer;

And thus we would have stored a pointer to some random IUnknown derivative in a pointer to IDirect3DSurface9. That would violate the C++ type system. Which is why casting any other type but T** to T** is not allowed. In other words, a variable of type T** can only be assigned a ppT (a value of type T**) and not a ppSomeSubytpeOfT.

Compare this one: How come a pointer to a derived class cannot be passed to a function expecting a reference to a pointer to the base class? And this one: Casting double pointers of base classes.

For COM SafeRelease, either a template (as suggested here) or a macro will do.

Community
  • 1
  • 1
Lumi
  • 14,775
  • 8
  • 59
  • 92