Delphi object instance lifetime demo; do not use AfterConstruction as a poor-mans way to work around non-virtual constructor or undetermined Create hierarchy calls

  

I think using AfterConstruction is a poor man’s solution that you should only use in exceptional cases, as it is:

called only after the last constructor in the chain is called.
called outside of the constructor chain (i.e. exceptions in it will not automatically call the destructor chain, nor BeforeDestruction)
meant to add any initialization code that requires a fully created object instance.

There were quite a few sites I  visited that were using AfterConstruction. Usage roughly falls into two cases:

Virtually all of their usage was distrusting all Create call chains to be called properly.
Introducing a new AfterConstruction call chain does not work around this problem.
In fact it adds just another problem of equal magnitude: a new call chain with the same trust on calling the inheritance chain.
Some teams use it as a replacement for a virtual constructor, or as a safeguard for developers calling the non-virtual constructor.
That kind of AfterConstruction usage now introduces a second call hierarchy, making the code more complex, to circumvent a problem you can easily solve:

introduce a virtual Create constructor in a base class
have that constructor set a field to ensure it is called
check that field at appropriate places, for instance in the Destroy destructor (see example below)

Destruction is important too
A surprising thing in many code bases, is that far less attention is paid to destruction.
Of the above sites, many classes had AfterConstruction. Very few of those had a BeforeDestruction counterpart, though a lot of them should have had one.
In the same light, I see many places where a destructor Destroy never got implemented, leading to all sorts of de-initialisation problems. The easiest problems they can cause are memory leaks, which are usually highly visible. Harder problems are for instance handles that are kept open.
Note that raised Exception instances are normally destroyed in the exception handling.
Finally, a lot of destructor and BeforeDestruction code is very sloppy at handling exceptional cases: not just handling exceptions themselves, but also handling partially initialised instances.
It pays to write unit tests for these cases, as it will save you from surprises.
Execution order
Two small examples in one lifetime demo.
Output

Without raising exceptions:
Create(AConstructorBehaviour = NoExceptionInConstructor)
AfterConstruction
BeforeDestruction
Destroy; FAfterConstructionWasCalled = True;
FreeInstance

With raising exception in constructor:
Create(AConstructorBehaviour = RaiseExceptionInConstructor)
Destroy; FAfterConstructionWasCalled = False;
FreeInstance

With raising exception in AfterConstruction:
Create(AConstructorBehaviour = NoExceptionInConstructor)
AfterConstruction
BeforeDestruction
Destroy; FAfterConstructionWasCalled = True;
FreeInstance
Exception: Error Message

Note the second part of the output does not call AfterConstruction nor BeforeDestruction. This is because when construction fails, the destructor is being called: a Delphi language feature that few people know about as on-line documentation is very hard in html documentation [WayBack] Delphi Language Guide, topic [WayBack] Classes and Objects, subtopic [WayBack] Methods: Constructors:

If an exception is raised during execution of a constructor that was invoked on a class reference, the Destroy destructor is automatically called to destroy the unfinished object.

That – very easy to overlook – one little sentence without any example is all to it, that – as of Delphi 10.1 Berlin – moved to [Archive.is] Methods (Delphi) – RAD Studio: Constructors.
A more elaborate part was put in the html documentation of RAD Studio 2010 C++ documentation subtopic “Object Destruction” quoted below.
Raising exceptions in constuctors
Given this class hierarchy:

type
A = class
end;
B = class(A)
end;
C = class(B)
end;

[Archive.is] Object Destruction – RAD Studio, limited to only the Pascal bits, then is like this:
Consider the case where an exception is raised in the constructor of class B when constructing an instance of C. What results in .. Object Pascal …is described here:


In Object Pascal, only the instantiated class destructor is called automatically. This is the destructor for C. As with constructors, it is entirely the programmer’s responsibility to call inherited in destructors. In this example, if we assume all of the destructors call inherited, then the destructors for C, B, and A are called in that order. Moreover, whether or not inherited was already called in B’s constructor before the exception occurred, A’s destructor is called because inherited was called in B’s destructor. Calling the destructor for A is independent of whether its constructor was actually called. More importantly, because it is common for constructors to call inherited immediately, the destructor for C is called whether or not the body of its constructor was completely executed.

The oldest (PDF) documentation I could find mentioning this is [WayBack] CB6_DevelopersGuide_EN.pdf (C++ Builder 6: Developer’s Guide – Documentation).
It still strikes me as odd that such an important Delphi language feature is only documented in the C++ product line.
Luckily others stepped into this void, all found through delphi raise in constructor calls destructor – Google Search:

[WayBack] Destructor Keyword – Delphi in a Nutshell [Book]
Delphi automatically calls Destroy if a constructor raises an exception. Therefore, you should program defensively. Fields might not be initialized when the destructor is called, so always check for a zero or nil value. Note that Free, FreeMem, and Dispose automatically check for nil before freeing the object or memory.
Note this is still a great book, despite its age. Go get it: [WayBack] Delphi in a Nutshell – O’Reilly Media (or via Delphi in a Nutshell: A Desktop Quick Reference (In a Nutshell (O’Reilly)): Ray Lischner: 0636920926597: Amazon.com: Books)
[WayBack] Delphi raise exception in constructor – Stack Overflow
when the constructor raises the exception, the destructor is called.

[WayBack] Think twice before raising exception in constructor | The Programming Works
if you raise an exception in a constructor be very careful when writing the destructor, cause you need to treat a case of improperly initialized instance.

There is also on-line information that puts you on the wrong foot:
[WayBack] Exceptions in Constructor and or AfterConstruction ? – delphi

But why than is BeforeDestruction not being call automatically when the the
exception is raised in AfterConstruction?

The above output shows this last observation is clearly wrong.
Detecting if methods got called
The second part of the output also shows you that you can detect the condition that AfterConstruction was indeed called (or in fact any method was called) from the Destroy (or any other method).
The code below shows that this is very easy to do.
Code

program ExceptionInConstructorConsoleProject;

{$APPTYPE CONSOLE}

uses
System.SysUtils,
System.Rtti;

type
TConstructorBehaviour = (RaiseExceptionInConstructor, NoExceptionInConstructor);
TAfterConstructionBehaviour = (RaiseExceptionInAfterConstruction, NoExceptionInConstructorAfterConstruction);
TExceptionInConstructorClass = class(TObject)
strict private
FAfterConstructionWasCalled: Boolean;
FAfterConstructionBehaviour: TAfterConstructionBehaviour;
public
constructor Create(const AConstructorBehaviour: TConstructorBehaviour; const AAfterConstructionBehaviour: TAfterConstructionBehaviour);
destructor Destroy(); override;
procedure AfterConstruction(); override;
procedure BeforeDestruction(); override;
end;

constructor TExceptionInConstructorClass.Create(const AConstructorBehaviour: TConstructorBehaviour);
begin
Writeln(Format(‘Create(AConstructorBehaviour = %s)’, [TRttiEnumerationType.GetName(AConstructorBehaviour)]));
inherited Create();
FAfterConstructionWasCalled := False;
FAfterConstructionBehaviour := AAfterConstructionBehaviour;
if AConstructorBehaviour = RaiseExceptionInConstructor then
raise Exception.Create(‘Error Message’);
end;

destructor TExceptionInConstructorClass.Destroy();
begin
Writeln(Format(‘Destroy; FAfterConstructionWasCalled = %s;’, [TRttiEnumerationType.GetName<Boolean>(FAfterConstructionWasCalled)]));
inherited Destroy();
end;

procedure TExceptionInConstructorClass.AfterConstruction();
begin
Writeln(‘AfterConstruction’);
inherited AfterConstruction();
if FAfterConstructionBehaviour = RaiseExceptionInAfterConstruction then
raise Exception.Create(‘Error Message’);
end;

procedure TExceptionInConstructorClass.BeforeDestruction();
begin
Writeln(‘BeforeDestruction’);
inherited BeforeDestruction();
end;

procedure TExceptionInConstructorClass.FreeInstance();
begin
Writeln(‘FreeInstance’);
inherited FreeInstance();
end;
begin
try
Writeln(‘Without raising exceptions:’);
with TExceptionInConstructorClass.Create(NoExceptionInConstructor, NoExceptionInConstructorAfterConstruction) do
Free();
Writeln;
Writeln(‘With raising exception in constructor:’);
try
TExceptionInConstructorClass.Create(RaiseExceptionInConstructor, NoExceptionInConstructorAfterConstruction);
finally
Writeln;
Writeln(‘With raising exception in AfterConstruction:’);
TExceptionInConstructorClass.Create(NoExceptionInConstructor, RaiseExceptionInAfterConstruction);
end;
except
on E: Exception do
Writeln(E.ClassName, ‘: ‘, E.Message);
end;
end.

Statistics
In the Delphi source directory (RTL + FMX + VCL + more), you see this count:

constructor Create: ~5000 occurrences
destructor Destroy: ~2300 occurrences
procedure AfterConstruction: ~400 occurrences of which ~100 relevant, as there are ~300 in unit FMX.DEA.Schema to perform XML schema setup right after the constructor is run.
procedure BeforeDestruction: ~25 occurrences.

In % ratios:

Create: 100%
Destroy: ~45%
AfterConstruction: 2%
BeforeDestruction: 0.5%

Related
The above were centered around these pieces of Delphi documentation:

[WayBack] Exceptions
[WayBack] TObject::Create Constructor
[WayBack] TObject::Destroy Destructor
[WayBack] TObject::AfterConstruction Method
[WayBack] TObject::BeforeDestruction Method
[WayBack] TObject::FreeInstance Method
Note: System::TObject::BeforeDestruction is not called when the object is destroyed before it is fully constructed. That is, if the object’s constructor raises an exception, the destructor is called to dispose of the object, but System::TObject::BeforeDestruction is not called.

If you want to dig deeper into the various methods involved in allocation, initialisation, construction, deconstruction, and de-allocation, then remember that in the Win32 realm (which is the central target for most Delphi developers) not much has changed since Delphi 2007.
Outside that realm, things do have changed. For that, read this great book, that taught me quite a few things: [WayBack] Delphi Memory Management eBook .
Besides the eBook, you can also get it as

Paperback: 377 pages
Publisher: CreateSpace Independent Publishing Platform (June 24, 2018)
Language: English
ISBN-10: 1721654909
ISBN-13: 978-1721654901

These are the relevant adapted from the Delphi 2007 [WayBack] TObject Members:

WayBack
Declaration
Name
Description

[WayBack]

procedure AfterConstruction; virtual;

AfterConstruction
Responds after the last constructor has executed.

[WayBack]

procedure BeforeDestruction; virtual;

BeforeDestruction
Responds before the first destructor executes.

[WayBack]

procedure CleanupInstance;

CleanupInstance
Performs finalization on long strings, variants, and interface variables within a class.

[WayBack]

constructor Create;

Create
Constructs an object and initializes its data before the object is first used.

[WayBack]

destructor Destroy; virtual;

Destroy
Disposes of an object instance.

[WayBack]

procedure Free;

Free
Destroys an object and frees its associated memory, if necessary.

[WayBack]

procedure FreeInstance; virtual;

FreeInstance
Deallocates memory allocated by a previous call to the System::TObject::NewInstance method.

[WayBack]

class function InitInstance(Instance: Pointer): TObject;

InitInstance
Initializes a newly allocated object instance to all zeros and initializes the instance’s virtual method table pointer.

[WayBack]

class function InstanceSize: Longint;

InstanceSize
Returns the size in bytes of each instance of the object type.

[WayBack]

class function NewInstance: TObject; virtual;

NewInstance
Allocates memory for an instance of an object type and returns a pointer to that new instance.

Combinining NewInstance with FeeInstance can be useful in for instance these cases:

singleton implementations like the the [WayBack] EInvalidPointer Class and [WayBack] EOutOfMemory Class that both descend from [WayBack] EHeapException Class:

EHeapException’s descendants—EOutOfMemory and EInvalidPointer—are used to handle failed allocations of dynamic memory and invalid pointer operations.
Note: Memory for these exceptions is pre-allocated whenever an application starts and remains allocated as long as the application is running. Never raise EHeapException or its descendants directly.

delaying actual free, for instance in a pool like these in the Delphi XE7 introduced [Archive.is] System.Threading.TThreadPool.TAbstractWorkerData – RAD Studio API Documentation with these methods:

[Archive.is] System.Threading.TThreadPool.TAbstractWorkerData.NewInstance – RAD Studio API Documentation
[Archive.is] System.Threading.TThreadPool.TAbstractWorkerData.FreeInstance – RAD Studio API Documentation

I wish the TAbstractWorkerData was more appropriately documented, as some really interesting stuff is going on in it.
–jeroen

Comments are closed.