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.