views:

243

answers:

1

When I build the following C++/CLI code in VS2008, a code analysis warning CA1001 is displayed.

ref class A
{
public:
    A()   { m_hwnd = new HWND; }
    ~A()  { this->!A(); }
protected:
    !A()  { delete m_hwnd; }
    HWND* m_hwnd;
};

ref class B
{
public:
    B()   { m_a = gcnew A(); }
protected:
    A^    m_a;
};

warning: CA1001 : Microsoft.Design : Implement IDisposable on 'B' because it creates members of the following IDisposable types: 'A'.

To resolve this warning, I would have to add this code to class B:

    ~B()  { delete m_a; }

But I don't understand why. Class A implements IDisposable via its destructor (and finalizer).
So surely whenever A gets garbage-collected, then A's finalizer or destructor will get called, freeing its unmanaged resources.

Why does B have to add a destructor to call 'delete' on its A member?
Will the GC only call A's destructor if B explicitly calls "delete m_a"?


Edit: it seems this works automatically if you use the "syntax sugar" method of declaring the A member, like this:

ref class B
{
public:
    B()   { }
protected:
    A     m_a;
};

but this is not always possible.

Why isn't the GC clever enough to automatically dispose of the managed reference pointer of A^, once no one else has a pointer to it?

+2  A: 

You should use stack semantics for the member and add a destructor to the containing class. Then the member will be disposed. See http://msdn.microsoft.com/en-us/library/ms177197.aspx

ref class B
{
public:
    B()   {}
    ~B()  {}
protected:
    A    m_a;
};

The member is still a ref. type and is still created on the heap.

Edit:

Dispose in .net is at best unfortunate, in C# the whole deterministic behaviour is broken and you have to be really rigerous with Dispose calls to get the behaviour most c++ developers expect.

In c++/cli stack semantics make it better. If you can't use them you are back to having to explicitly call dispose which in c++/cli is represented by the destructor.

The only way to automatically chain dispose calls to members is through stack semantics if the members are normal managed pointers just like c# you'll have to chain the calls manually.

Many classes could hold the same A^ pointer, there is no way to know which one should call the destructor.

You get the warning because you have implemented the destructor which causes your class to implement IDispose. This gives you a chance to clean up in a deterministic manner.

The GC alone can only collect an object with no references and call the finalizer. This is far from deterministic. Note that relying on the finalizer to do the clean up should be a safety net only as it may be called a long time in the future if at all.

I would recommend trying to design your code to allow the above pattern.

morechilli
Thanks, and it's true that I could use the syntax sugar method - actually if I do this then I don't need to implement a destructor at all (well it suppresses the warning anyway). But in some/most situations you can't use the syntax sugar method, you can only store the managed reference pointer. I want to know why the GC needs an explicit call to delete the managed reference pointer in order to dispose the managed object.
demoncodemonkey
"Many classes could hold the same A^ pointer, there is no way to know which one should call the destructor" << The GC should be able to know which one was the last one, and when the last one gets freed then it should call the destructor. It would appear the GC is not as amazing as it could be...
demoncodemonkey
I'll check if I can refactor my code like you said.
demoncodemonkey