Thursday, September 3, 2009

Class (, Static, or Shared) Constructors (and Destructors)

In my last post, I hinted at a new native Delphi language feature, class constructors and destructors. For those familiar with, say, C# or VB.NET, they are called a ‘static’ constructor (C#), or a ‘shared’ constructor (VB.NET). Let’s now take a closer look at them in terms of how they’re useful along with a little hint at how they’re implemented. Class constructors had been available in Delphi Prism, but until Delphi 2010, they were notably absent from native Delphi. The main reason for this was that the .NET runtime controlled and defined when a ‘static’ or ‘shared’ constructor was invoked. In native Delphi there was no defined mechanism to do the same.

From the C# documentation:

Called automatically to initialize the class before the first instance is created or any static members are referenced. The constructor cannot be called directly.

From the VB.NET documentation:

Shared constructors initialize a type's shared variables; they are run after the program begins executing, but before any references to a member of the type. A shared constructor specifies the Shared modifier, unless it is in a standard module in which case the Shared modifier is implied.

Since the .NET runtime is a garbage-collected environment, there are no references or mention of ‘class destructors’. Because native Delphi is is not garbage collected and requires explicit destruction of instances and freeing of memory, it stood to reason that for congruency, native Delphi would also have the notion of a class destructor.

First of all let’s look at the syntax:

type
EDliException = class(Exception)
private
class constructor Create;
class destructor Destroy;
end;


 


Technically, it does not matter where in the class declaration they are declared (private, public, protected, published) since, as the  documentation above states, “The constructor [destructor (added)] cannot be called directly.” So in this example, I just made them private to merely make it more clear in the source that they cannot be called. Just like the other kinds of ‘class’ methods, the body of theses methods can only access other class methods or variables. They also have no implicit “Self”, so you cannot call virtual class methods in a polymorphic manner. You can have one and only one class constructor/destructor per class type. As we’ll see in a moment, you should treat the class constructor/destructor methods in a manner similar to a unit’s initialization/finalization section. That’s it. It’s pretty simple, really.


Now let’s look at why these are useful. There were several main reasons for adding this to the language. They were:



  1. Improved encapsulation

  2. Better control over smart-linking

  3. Better language compatibility with Delphi-Prism

Improved encapsulation


How many times have you needed to initialize something related to a specific class? What if your class is using the singleton pattern and you need to create that single instance? How about registering the class with a “class-factory?” Prior to Delphi 2010, there were several options available to you, none of which were really in keeping with the OOP notion of encapsulation. You could put all the code into the initialization section of the unit in which the class lived, add a regular class method and call it explicitly, or manually integrate the init code into your application’s startup. By moving all this code into a class constructor, the mere act of “touching” the class will cause the class constructor to run in keeping with the above rules.


Better control over smart-linking


Loosely coupled with improved encapsulation is the notion that by moving any initialization code into the class constructor you have a little more control over what code is linked into your application. You can now take advantage of the fact that as long as the class type isn’t referenced referenced anywhere in your code, none of the code related to the class type is linked in, including the VMT, virtual methods, RTTI, etc.. If you had placed this init code into the initialization section of its containing unit, then at least the VMT, virtual methods and RTTI would have been linked into your application. Even though none of your code references it. The new enhanced RTTI throws an interesting wrinkle to this theory, but that will have to be covered later.


Better language compatibility with Delphi Prism


As Delphi Prism gains popularity, more people will need to maintain some of their code in a manner that they can compile with either compiler. There are still many language constructors that both compilers have that the other doesn’t, but this is a continuing effort to close that gap where it makes sense. Expect to see more work in this area, in both compilers.


In my next post, we’ll “pop the hood” (or bonnet, for our good friends across the pond) and take a peek at how they work and exactly when they are invoked. I will say that many people tend to read way, way too much into this simple statement describing when a class constructor is invoked; “Called automatically to initialize the class before the first instance is created or any static members are referenced.” The only temporal reference in that statement is “…before…”. I will leave you to consider the implications of that word, lest many of you begin to talk about “threading issues” and “code-bloat.” Class constructors and destructors are to classes as initialization and finalization are to units.

6 comments:

  1. Can class constructors and destructors be virtual? Can they call an inherited class constructor or destructor? I assume that because they are called automatically, they can't have any parameters. Is that correct?

    ReplyDelete
  2. They cannot be virtual.

    They cannot have any parameters.

    You do not *need* to call the inherited class constructor since by virtue of the fact that your class is a descendant of another class that has a class constructor, you've "touched" it. In that case the ancestor's class constructor will have already been called for you.

    ReplyDelete
  3. I think that constructors and destructors should be class since beginning of times, I thougth contructor directive made that change to the function.

    ReplyDelete
  4. @Miguel,

    When a traditional constructor is called the framework allocates memory for a new instance, zeros it, and then lets the constructor code initialise that instance. Likewise a destructor finalises an instance.

    Class constructors/destructors act on classes rather than instances just as class procedures do. What's difference is that they are called at most once and called automatically by the framework.

    ReplyDelete
  5. Allen,

    Please consider the following Initialization Section:

    Var
    ClassRegistry : TgClassRegistry;

    Initialization
    ClassRegistry := TgClassRegistry.Create([TCustomer, TVendor, TInvoice]);

    Finalization
    ClassRegistry.Free;

    My initial impression was that, using a class constructor, I should be able to replace the initialization and finalization code by doing something like this:

    TgBase = Class
    Class Constructor Create;
    Class Destructor Destroy;
    End;

    TCustomer = Class(TgBase)
    ...
    End;

    TVendor = Class(TgBase)
    ...
    End;

    TInvoice = Class(TgBase)
    ...
    End;

    Class Constructor TgBase.Create;
    Begin
    ClassRegistry.Register(Self);
    End;

    Class Destructor TgBase.Destroy;
    Begin
    ClassRegistry.Unregister(Self)
    End;

    From your description, it doesn’t sound like Self exists for class constructors. If it did, this would have saved me from having to write the initialization and finalization sections, and also from having to remember to register newly added classes.

    Can you think of another way to achieve the same results? If not, would you consider adding Self to class constructors and destructors in a future version?

    Thanks,

    Steve

    ReplyDelete
  6. [...] Class (, Static, or Shared) Constructors (and Destructors) [...]

    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.