Thursday, September 3, 2009

Class Constructors. Popping the hood.

My last post introduced class constructors and destructors, a new language feature in Delphi 2010. I mainly covered what they were and what kinds of things they’re good for. In this post I’ll cover some of how they’re implemented. I’m sure the question on everyone’s mind is when and how they’re invoked? If you were patient enough to read my entire post, I provided a hint as to the inner workings. The rule for when they run is simple:

    • All eligible class constructors and class destructors are invoked in sequence with unit initialization and finalization, respectively.
    • If a given class constructor or destructor is eligible to be invoked (ie. it was linked into your application), it will run immediately before the initialization section for the unit in which the class is implemented. The class destructors will be invoked immediately after the finalization section for the unit in which the class is implemented.
    • Class constructors in a given unit are generally invoked in the same order of declaration, except in cases described below. Class destructors are invoked in reverse order from the class constructors.
    • For an ancestor class declared in the same unit, its class constructor will be invoked before the descendant class constructor and the class destructor is invoked after the descendant class destructor.
    • If the implementation of given class constructor references another class in the same unit with a class constructor, the referenced class’ class constructor will be invoked before the current class’ class constructor. If the references are cyclic (ie. they reference each other in their class constructors), then they are invoked in reverse order of declaration. This means that there can be cases where a class constructor can reference an “unconstructed” class.
    • Ancestor classes from external units used in the interface section are guaranteed to already have their class constructors run prior to any class constructors on descendant classes within the current unit.
    • Unit cycles can break down the deterministic nature of the above rules in the same manner as unit initialization and finalization. However, for a given unit, it is guaranteed that all the class constructors declared within in it will have already run immediately before the initialization section runs. Congruent to this rule is that it is guaranteed that all the class destructors will run immediately after the finalization section.
    • Dynamically loaded packages, using LoadPackage. Because there is no way to know exactly which classes are going to be used, just like there is no way to know which units are going to be used, all class constructors and destructors along with all unit initialization and finalizations are invoked according to the above rules.

Other rules about their use are:

    • You do not have to declare a class destructor if you declare a class constructor, and vice versa.
    • They cannot be virtual, dynamic or message.
    • They cannot be explicitly called.
    • They cannot have any parameters.
    • They do not have to be called Create and Destroy. (ie. Init and Fini are equally valid names).

With this implementation, it was easier to leverage the same table that the compiler creates for unit initialization and finalization. It satisfies this requirement: “Called automatically to initialize the class before the first instance is created or any static members are referenced.” Issues with cycles are also clearly warned against in VB.NET and C#: “Avoid circular references in Shared … since it is generally impossible to determine the order in which classes containing such references are loaded.”

Another benefit is that since it is running during the initialization/finalization phases, any threading implications are no different than the existing rules regarding unit initialization and finalization.

12 comments:

  1. I'd like to see all this in the Delphi 2010 Help.

    ReplyDelete
  2. Hi Allen,

    I really love this feature, yesterday I did a refactory, and move some code from initialization/finalization to class constructor/destructor.

    Hey, what about to post your articles in docwiki too?
    Then our Help files will be update eventually.

    Thank you, and please, keep posting.

    ReplyDelete
  3. Interesting. I'd be interested to hear why you decided to call them *before* the unit initialization. My first thought would have been to run unit initialization first, so it can initialize any global variables that the class constructors might rely on. For example, the unit initialization might want to instantiate a TList that the class constructors then add themselves to.

    Obviously there are trade-offs either way, and the way you've got it will have some advantages of its own. And the scenario I outlined is easy enough to implement by just adding another class (even a unit-private one) with its own class constructor. But still, I'd be curious to hear some of the reasons and thought processes that led to your making that choice. You have to think things out more than I do.

    Love the transparency. Looking forward to hearing more like this!

    ReplyDelete
  4. I guess there can only be one class constructor/destructor for each class?

    ReplyDelete
  5. Joe,
    You could argue either way, but I had to pick an order. We went back and forth on it until we decided that it is possible that the unit initialization may need to have those classes initialized first, and in fact that may be more likely that the other way around. If a given class needs some global variable initialized, then it should just do it itself.

    ReplyDelete
  6. @Thomas: Yes. Allen mentioned this in the other post.

    "You can have one and only one class constructor/destructor per class type"

    ReplyDelete
  7. Class constructors being invoked before unit initialization really does need to be well-documented, as it runs counter to natural expectations (unit->class->instance).

    And why aren't class constructors even mentioned in the D2010 help?! I just spent a few minutes trying to see what the help says about them, and cannot find any mention in the index or "What's New" about this rather significant feature...

    ReplyDelete
  8. I think it should be printed in BOLD BIG RED in the documentation that, when writing DLL, class constructor / destructor generally must not: operate on synchronization objects, wait for threads, load / unload DLL. Because it is a part of DLLMain, just like unit initialization / finalization.

    ReplyDelete
  9. You missed to mention that there is no support for class constructors if C++Builder output files (bpi, lib,
    obj) is set as output format.

    ReplyDelete
  10. [...] such a big overhead in Delphi 2010. You can read more about class constructors and destructors in this blog post by Allen [...]

    ReplyDelete
  11. [...] Sinn eines Class Constructors (erst recht eines Class Destructors) noch nicht ganz verstanden, aber hier stehen zumindest ein paar Regeln, die man beachten soll: "You do not have to declare a class [...]

    ReplyDelete
  12. What use is a class destructor, if all class vars are finalized by the time it is called? I guessed it was the place to free objects contained in the class vars, but oviously I was wrong...

    ReplyDelete

Please keep your comments related to the post on which you are commenting. No spam, personal attacks, or general nastiness. I will be watching and will delete comments I find irrelevant, offensive and unnecessary.