3

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?

brickner
  • 6,595
  • 3
  • 41
  • 54
demoncodemonkey
  • 11,730
  • 10
  • 61
  • 103

1 Answers1

2

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
  • 9,827
  • 7
  • 33
  • 54
  • 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 Aug 12 '09 at 13:16
  • "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 Aug 13 '09 at 08:56
  • @demoncodemonkey: Two major problems with "let the GC do all the work", are (1) after the last reference to an object is eliminated, an arbitrary amount of time may elapse before the garbage-collector gets around to the area of memory containing it, and having the object go that long without being cleaned up might be unacceptable (2) the garbage collector can what references are reachable, but it has no way of knowing what references are "useful". Suppose, for example, an object exists to count how many times a form raises its `Paint` event. Every time `Paint` is raised... – supercat Aug 06 '12 at 15:57
  • ...the object increments a counter. The form will need to keep a reference to the object so it can send `Paint` events, but the form doesn't really care about the object. If no reference to the object is kept by anything that cares about the counter, the object will cease to serve any useful purpose, but the GC will have no way of knowing that. If many such objects are created and abandoned in the lifetime of a form, they may constitute a serious memory leak. – supercat Aug 06 '12 at 16:01