Monday, March 20, 2006

Virtual methods and inherited

Delphi has unusually rich language support for polymorphic behaviour. The most straightforward and the one that most programmers will associate with polymorphism, is the virtual method. A virtual method is declared in a base class using the virtual directive:

TShape = class
procedure Draw(Canvas: TCanvas); virtual;
end;

The base class may or may not have a default implementation for the virtual method. If it doesn’t you mark it abstract, forcing all instantiated descendent classes to override the method.

TRectangle = class(TShape)
procedure Draw(Canvas: TCanvas); override;
end;

All this is basic stuff that all Delphi programmers know. Depending on the implementation (and documentation!) of the base class, the descendent class may decide to call the base class method before, (more rarely) in the middle, or after its own Simplementation (or not at all). There are two ways to call the inherited method, with subtle differences:

procedure TRectangle.Draw(Canvas: TCanvas);
begin
inherited Draw(Canvas);
Canvas.Rectangle(FRect);
end;

This will unconditionally call the inherited Draw method in the base class. If the base class method is abstract, this will fail at run-time with an EAbstractError exception - or a RTE (RunTimeError) 210 - if you don't use (the exception system in) SysUtils.

The alternative syntax is to call just "inherited;", like this:

procedure TRectangle.Draw(Canvas: TCanvas);
begin
inherited;
Canvas.Rectangle(FRect);
end;

When the parent class is non-abstract this will work identically as above, passing the same parameters that the current routine was passed. This is also the calling pattern inserted by code completion when implementing an overridden method. It is also used by the IDE when inserting event handlers in forms that use visual inheritance.

If the base class method is abstract, or if the base class does not contain the method at all (for non-virtual methods), the “inherited” call becomes a noop (No-Operation). The compiler generates no code for it (and thus you cannot set a breakpoint on it). This mechanism is part of the Delphi language’s excellent version resiliency.

One caveat with the "inherited;" syntax is that it is not supported for functions. For functions you must use the explicit syntax including the method name and any arguments. For instance:

type
TMyClass = class
function MethodC: integer; virtual;
end;
TMyDescendent = class(TMyClass)
function MethodC: integer; override;
end;

function TMyClass.MethodC: integer;
begin
Result := 43;
end;

function TMyDescendent.MethodC: integer;
begin
// inherited; // Error
// Result := inherited; // Error
Result := inherited MethodC; // Ok
end;

This might look like an oversight in the Delphi language design, but I think it is deliberate. The rationale behind it is probably that if TMyClass.MethodC is abstract (or made abstract in the future), the Result assignment in the descendent class will be removed, and thus Result has suddenly undefined value. This would certainly cause subtle bugs.

However, I think there is a small hole in the inherited call syntax. In many ways a procedure that takes an out parameter (and in some cases var parameter) behaves like a function returning a result. So in my opinion the inherited; syntax should be prohibited when calling methods with out (and maybe var) parameters. But it isn’t.

procedure TMyDescendent.MethodB(out A: integer);
begin
inherited;
Writeln(A);
end;

This means that if the parent class’ method is abstract (or just missing in the case of non-virtual methods), the value of the out parameter will be undefined. In my opinion, the compiler should forbid such inherited; calls, requiring the explicit inherited MethodB(A) syntax. The cat is already out, however, and making this a compile-time error will surely break existing code, so it should probably be made a warning instead.

[Updated: Delphi syntax highlighting provided by DelphiDabbler PasH]

4 comments:

Fabricio said...

Code which uses out parameters are very rare, even in VCL. At least, in my experience I never seem people using that type of parameter. Most doesn't ever know that keywork exists.

If the compiler ever breaks some code, it would be very little.

Mark Patterson said...

I agree with Hallvard. I use out parameters, and in fact var parameters, which most people are aware of, carry the same risk.

An out parameter is simply a var parameter that you don't expect to have a value when its routine is called. The routine is expected to supply a value.

xorin said...

just what i was looking for. thank man :D

Atilla said...

In fact, there's a big problem with this type of inheritance. There's no difference between deliberately not calling the inherited method and FORGETTING to call it.

It could be easily solved by adding another keyword, like "ignore inherited". Then compiler would know you didn't forget to call the inherited method, but you deliberately choose not to call it.

The way it is now, it's damn easy to forget calling the inherited method in cases when it really has to be called.



Copyright © 2004-2007 by Hallvard Vassbotn