• No se han encontrado resultados

Formación e Información en el Sector de la Construcción

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.

Documento similar