As we saw earlier, a ‘constructor’ is a method that creates an object. Constructors for class-based objects are very flexible things: aside from being both overloadable and nameable to almost anything you fancy, they can also be virtual. More dubiously, it is also possible for a descendant class to completely ignore any constructor its parent introduced. Nonetheless, the convention is still to stick to one name (Create) and call the inherited constructor in the very first line of
your own: constructor TMyObject.Create; begin inherited Create; //do stuff... end;
Since every class ultimately descends from TObject, TObject introduces a do-nothing constructor called Create, the snippet
above will always be valid code: if neither the parent class nor any other descendant has a parameterless constructor called Create, TObject’s will get called. This works in exactly the same way as an inherited call would work in an ordinary
method.
The syntax for declaring virtual and/or overloaded constructors follows that for ordinary methods too:
type
TMyObject = class
constructor CreateVirtual; virtual;
constructor CreateVirtualAndAbstract; virtual; abstract; constructor CreateOverloaded; overload;
constructor CreateOverloaded(AParam: Boolean); overload; end;
Since the rules for normal methods apply, virtual constructors of an ancestor class will by default be ‘hidden’ if a child class defines its own with the same name. The compiler will warn you of this when it happens; to remove the warning, do what you must do in the case of an ordinary method: either add the reintroduce directive to make clear you realise
you’re hiding an inherited member, or override the inherited constructor to be able to add the override directive to it.
For example, the TComponent class defines a virtual constructor called Create that takes an Owner parameter. If in a
descendant class you wished to define an additional variant that took an extra parameter, you could either give it a different name...
type
TMyComp = class(TComponent) public
constructor CreateExt(AOwner: TComponent; Extra: Byte);
...or ‘retrospectively’ overload the inherited Create: type
TMyComp = class(TComponent) public
constructor Create(AOwner: TComponent); overload; override; constructor Create(AOwner: TComponent; Extra: Byte); overload;
//...
constructor TMyComp.Create(AOwner: TComponent); begin inherited Create(AOwner); end; Notice the override of the inherited constructor requires an implementation, though that implementation need only be to call the parent class’ constructor of the same signature. This again follows the pattern for ordinary methods, and there’s a good reason for that: a Delphi constructor in itself is in fact not much more than an ordinary instance method. Consider the following code, which is formally valid if hardly advisable: procedure TDodgy.Foo;
var I: Integer; begin for I := 0 to 4 do Create; end;
Contrary to what things may seem, this will not instantiate five new instances of TDodgy. Instead, it will just call Create five
times on the current instance. To actually use a constructor as a constructor requires calling it against a class rather than an object reference. Typically, a class reference means a class identifier:
function TNotSoDodgy.Foo: TArray<TNotSoDodgy>; var I: Integer; begin SetLength(Result, 5); for I := 0 to 4 do Result[I] := TNotSoDodgy.Create; end; Unlike the previous snippet, this version will create five new instances of the class.
Destructors
As instances of a class must be explicitly constructed, so they must be explicitly destructed (‘destroyed’, ‘freed’). Similar to constructors, destructors in Delphi can be named as you wish. However, unlike constructors, there’s no sane reason to avoid the standard Destroy, which is introduced as a virtual method by TObject:type
TDestroyTest = class strict private FData: string; public
destructor Destroy; override; end;
destructor TDestroyTest.Destroy; begin
//do custom finalisation here... inherited Destroy;
end;
As shown here, the usual pattern is to call the inherited implementation of Destroy at the end of the override, not its start.
Having created an object, you should not actually call Destroy to destroy it however. Rather, call Free, which checks the
object reference isn’t nil before calling Destroy itself. (We will come onto the importance of this shortly.) The basic usage
pattern for locally-created objects is therefore this — the try/finally statement is used to ensure Free will be called even
if an exception is raised: procedure WorkWithObj; var TempObj: TMyObject; begin TempObj := TMyObject.Create; try
//work with TempObj finally
TempObj.Free; end;
end;
Note the object is created outside of the try statement. This is because the low-level RTL will ensure any exception raised
during the constructor will cause Destroy to be called automatically, after which the exception is re-raised and nothing
returned.
When more than one local object is created at the same time, you can set up multiple try/finally blocks like this: procedure WorkWithTwoObjsA;
var
Obj1, Obj2: TMyObject; begin
Obj1 := TMyObject.Create; try
Obj2 := TMyObject.Create; try
finally Obj2.Free; end; finally Obj1.Free; end; end;
Alternatively, you can define only the one, and initialise all but the first object reference to nil before the first Create call
is made:
procedure WorkWithTwoObjsB; var
Obj1, Obj2: TMyObject; begin
Obj2 := nil;
Obj1 := TMyObject.Create; try
Obj2 := TMyObject.Create; //work with the objects... finally
Obj2.Free; Obj1.Free; end;
end;
Setting the other references to nil prior to the try block is necessary because local variables aren’t automatically
initialised to anything, and Free can only tell the object doesn’t exist if its reference is nil.