How do I write proper destructors and finalizers?
Asked Answered
T

1

7

I am trying to figure out how to properly clean up after my objects in C++/CLI.

I have read or skimmed these two articles (one, two) and looked at the standard and looked at some other questions notably this one.

I have various pieces of information:

  1. The finalizer should clean up unmanaged resources (so everything gets cleaned up when the object is garbage-collected.
  2. The destructor should clean up managed resources (delete Foo or Foo.Dispose()?) and call the finalizer (according to 1)
  3. Both the destructor and the finalizer can be called multiple times (see 3 p. 26 end of 8.8.8)
  4. If the destructor is called the finalizer won't be called anymore (according to 1) (not by the CLR, that is, you can still call it yourself)
  5. The destructor will call base class destructors (see 3 p. 25)
  6. A class that has a finalizer should always have a destructor (presumably to deterministically clean up the unmanaged resources)
  7. A call to a finalizer will not call the base class finalizer (3 19.13.2 p. 131)

But there is also much confusion caused in part by the fact that

  1. finalizers are called destructors in C#
  2. the destructor internally generates Dispose and Finalize methods (not sure about Finalize) but the Finalize method is NOT the finalizer
  3. the semantics of destructors are different in C++ and the complexity of having both deterministic clean-up und garbage collection in general

What I would like as an answer is an example of a class with all the different kinds of data that it might contain (managed, unmanaged, managed but disposable, whatever else you can think of) and properly written destructor and finalizer.

I have two more specific question:

  1. Would it be acceptable to deal with the possibility of being called multiple times by just having a bool hasBeenCleanedUp member and making the entire code in the destructor/finalizer conditional on that?
  2. What kind of data can only be cleaned up by the destructor but must not be cleaned up in the finalizer because it may have been cleaned up by the gc?
Teahan answered 28/10, 2013 at 12:16 Comment(1)
You'll get a much better response if you give the example with the different kinds of data, and show us your attempt, and then we can give feedback.Loppy
N
5

Not a complete answer to your question but too long to fit a comment.

In a fully managed world, where each object only references managed objects, there is no need for finalizers or destructors, because the only resource is memory and the GC takes care of it.

When you reference unmanaged resources you have the responsibility of releasing them when you don't need them anymore.

So you need to implement a dedicated cleanup code.

There is 2 possibilities:

  • you know when you don't need the unmanaged resources anymore so you can deterministically run your cleanup code, this is implemented through destructors/Dispose

  • you don't know when these resources won't be needed anymore so you postpone the cleanup at the last possible moment, when the object that wraps the resources is collected by the GC, this is implemented through finalizers

You guess it's far better to be in the first situation because you don't consume more memory than you need and you avoid some additional overhead to the GC process.

You typically implement both because the life-time of the instances can vary from usage to usage.

At the CLR level there is no such things as deterministic cleanup, only finalizers.

At the language/API level there is support for deterministic cleanup:

  • in native C++ you have destructors called either when exiting a scope or when "deleting"

  • in the .Net world you have the Dispose pattern

  • in the pure managed C++/CLI world destructors are mapped to Dispose

When you have the chance to know exactly when you can run the cleanup code you call (or let the infrastructure call) the destructor. Once the cleanup is finished you can get rid of all the finalization process, so that the object can be collected immediately at the next GC.

Some clarifications about your first series of points:

  1. Yes

  2. The destructor is responsible for the cleanup of unmanaged resources too; it can call the finalizer if it's where you've factorized the cleanup code.

  3. They technically can but logically you should prevent it with a simple boolean guard

  4. Yes because all the cleanup should be done, so you ask the CLR not to finalize the object

  5. Yes because the base class knows which resources it has allocated

  6. Yes this is for deterministic cleanup

  7. You should ensure it's the case

And the others:

  1. Yes ~MyClass is mapped to an override of the Finalize method

  2. As said above the destructor is mapped to Dispose, but you should implement the finalizer yourself: !MyClass

  3. Summary: C++ destructors and Dispose pattern are for deterministic cleanup, C# destructors, C++/CLI finalizers are for non deterministic cleanup triggered by the GC.

Nitrogen answered 3/11, 2013 at 16:17 Comment(1)
Please note that ~MyClass is mapped to Dispose while !MyClass is mapped to Finalize method! - Microsoft Doc. The Dispose method is generated automatically by compiler and calls GC::SuppressFinalize therefore !MyClass should be called explicitly in ~MyClass method if you need the finalized to be called!Trysail

© 2022 - 2024 — McMap. All rights reserved.