views:

184

answers:

2

I have reintroduced the form constructor in a base form, but if I override the original constructor in a descendant form, the reintroduced constructor is no longer visible.

type
  TfrmA = class(TForm)
  private
    FWndParent: HWnd;
  public
    constructor Create(AOwner: TComponent; const AWndParent: Hwnd); reintroduce; overload; virtual;
  end;

constructor TfrmA.Create(AOwner: TComponent; const AWndParent: Hwnd);
begin
  FWndParent := AWndParent;
  inherited Create(AOwner);
end;

type
  TfrmB = class(TfrmA)
  private
  public
  end;

type
  TfrmC = class(TfrmB)
  private
  public
    constructor Create(AOwner: TComponent); override;
  end;

constructor TfrmC.Create(AOwner: TComponent);
begin
  inherited Create(AOwner);
end;

When creating:

  frmA := TfrmA.Create(nil, 0);
  frmB := TfrmB.Create(nil, 0);
  frmC := TfrmC.Create(nil, 0); // Compiler error

My work-around is to override the reintroduced constructor or to declare the original constructor overloaded, but I'd like to understand the reason for this behavior.

type
  TfrmA = class(TForm)
  private
    FWndParent: HWnd;
  public
    constructor Create(AOwner: TComponent); overload; override;
    constructor Create(AOwner: TComponent; const AWndParent: Hwnd); reintroduce; overload; virtual;
  end;

type
  TfrmC = class(TfrmB)
  private
  public
    constructor Create(AOwner: TComponent; const AWndParent: Hwnd); override;
  end;
+5  A: 

All methods declared with the same name need to be overloaded. The scope is the current class declaration.

The simplest solution is to use the overload directive again:

type
  TfrmC = class(TfrmB)
  private
  public
    constructor Create(AOwner: TComponent); overload; override;
  end;

The reason is because the original constructor in TCustomForm was not declared as overloaded.

TOndrej
+7  A: 

In your initial code your Create constructor hides the original Create. Which is why the compiler complains about TfrmC overriding a constructor it can no longer see from there.

You would normally get a compiler message about this, but "reintroduce" suppresses this. Whenever you need "reintroduce" to suppress compiler messages, alarm bells should go off as it is an indication that you are breaking polymorphism. Doesn't mean you shouldn't use it, but you should be aware of the implications.

In these cases, if I want to retain the ability to override the original constructor, I tend to add a different constructor, ie

constructor CreateWithParent(AOwner: TComponent; const AWndParent: HWnd); virtual;

and call the original constructor in its implementation. It does mean I need to call a different constructor, but as you already need to pass another parameter, that doesn't seem like such a bad trade-off.

Edit: As I mentioned in the comments, the compiler complains about tfrmC.Create(nil, 0); having too many parameters. It seems as though tfrmC.Create(AOwner) hides tfrmA.Create(AOwner, AWndParent); Thinking about it on the way home (traffic jams seem to have an advantage after all), there is an explanation for this.

1) The overload on the tfrmA.Create serves no direct purpose other than to allow introduction of other constructors with the same name. 2) The TfrmC constructor effectively hides the TfrmA constructor, reintroducing the signature that was hidden by the TfrmA constructor. In effect this isn't an override of the original constructor but a re-introduction of a re-introduced one. 3) I feel the compiler should have emitted a warning about this hiding. The reason it doesn't is possibly the overload directive on the TfrmA constructor. When you remove that, you get an error about the TfrmC.Create declaration differing from the previous one. 4) As the TfrmA constructor was hidden by TfrmC the compiler correctly complains about TFrmC.Create(nil, 0) having too many parameters.

What is described under 2) becomes obvious when you add the signature of the TfrmA constructor to TfrmC with just the override directive. This constructor is then immediately redlined by the IDE. The reason: two constructors with the same name and a missing overload directive for the TfrmC constructor that only has the AOwner parameter.

It also becomes clear when you comment out the second parameter in the instantiation of tfrmC and had only coded "inherited;" instead of "inherited Create(AOwner);" in its implementation. The compiler then complains about incompatible types.

The latter could have been solved by using either "inherited Create(AOwner);" (as you did) or "inherited Create(AOwner, 0);". While the latter would have ensured a walk back up the inheritance tree, the former would have jumped straight back to TForm1.Create.

Using a slightly modified version of your code and provided TfrmC is instantiated with a single parameter, the result of "inherited Create(AOwner)" would have been:

alt text

while the result of "inherited Create(AOwner, 0);" would have been: alt text

Adding overload to the TfrmC constructor as I suggested in the comments overrides the original TForm1.Create and allows for two constructors with the same name. This can be illustrated in the test project by turning on the TFRMC_OVERLOAD and INSTANTIATE2 conditional defines. This instantiates TfrmC with TfrmC.Create(nil, 0) and results in:

alt text

Which shows TfrmB.Create being called for TfrmC as TfrmB is the first ancestor with an implementation for the two-parameter constructor. While instantiating TfrmC with TfrmC.Create(nil) (turn the INSTANTIATE2 conditional define back off), results in the same picture as the first or second (depending on the TWOPARAMS define).

Marjan Venema
I understand that I've hidden the original constructor, and suppressed the compiler warning, but why is it no longer visible if the original constructor is then overridden? Does the declaration supercede the original hiding?
avenmore
Forgot to note: the compiler doesn't complain about overriding the hidden constructor, the constructor that did the hiding gets hidden.
avenmore
The compiler complains about too many parameters on the TfrmC.Create, which would indeed indicate that it no longer sees the TfrmA introduced constructor. The message is a bit confusing, but it still boils down to a missing overload directive. You don't need to add the overload on the original constructor in TfrmA though. It is sufficient to add it to TfrmC: constructor Create(AOwner: TComponent); overload; override;
Marjan Venema
Thank you for the detailed examples and code.I still have the feeling that the the overload of the original constructor should have been inferred or the later override prevented by the compiler.
avenmore