8

Update: The example i originally had was kind of complex. Here's a simple 8 line example that explains everything in one code block. The following does not compile gives a warning:

TComputer = class(TObject)
public
    constructor Create(Cup: Integer); virtual;
end;

TCellPhone = class(TComputer)
public
    constructor Create(Cup: Integer; Teapot: string); virtual;
end;

Note: This question is part 3 in my ongoing series of questions about the subtlties of constructors in Delphi

Original question

How can i add a constructor to an existing class?

Let's give an hypothetical example (i.e. one that i'm typing up in here in the SO editor so it may or may not compile):

TXHTMLStream = class(TXMLStream)
public
   ...
end;

Further assume that the normal use of TXHTMLStream involved performing a lot of repeated code before it can be used:

var
   xs: TXHTMLStream;
begin
   xs := TXHTMLStream.Create(filename);
   xs.Encoding := UTF32;
   xs.XmlVersion := 1.1;
   xs.DocType := 'strict';
   xs.PreserveWhitespace := 'true';
   ...

   xs.Save(xhtmlDocument);

Assume that i want to create a constructor that simplifies all that boilerplate setup code:

TXHTMLStream = class(TXMLStream)
public
    constructor Create(filename: string; Encoding: TEncoding); virtual;
end;

constructor TXHTMLStream.Create(filename: string; Encoding: TEncoding);
begin
   inherited Create(filename);
   xs.Encoding := Encoding;
   xs.XmlVersion := 1.1;
   xs.DocType := 'strict';
   xs.PreserveWhitespace := True;
   ...
end;

That simplifies usage of the object to:

var
   xs: TXHTMLStream;
begin
   xs := TXHTMLStream.Create(filename, UTF32);
   xs.Save(xhtmlDocument);

Except now Delphi complains that my new constructor hides the old constructor.

Method 'Create' hides virtual method of base type 'TXMLStream'

i certainly didn't mean to hide the ancestor create - i want both.

How do i add a constructor (with a different signature) to a descendant class, while keeping the ancestor constructor so it can still be used?

Ian Boyd
  • 246,734
  • 253
  • 869
  • 1,219

4 Answers4

8

My immediate reaction is to use the overload keyword, as in:

TCellPhone = class(TComputer)
public
    constructor Create(Cup: Integer; Teapot: string); reintroduce; overload; virtual;
end;

Edit: Thanks Ian for the edit, which makes an answer out of my answer. I would like to think that I got it for bravery, so I am going to contribute a fuller example:

program Project1;

{$APPTYPE CONSOLE}

uses
  SysUtils;

type

TComputer = class(TObject)
public
    constructor Create(Cup: Integer); virtual;
end;

TCellPhone = class(TComputer)
public
    constructor Create(Cup: Integer; Teapot: string); reintroduce; overload; virtual;
end;

{ TComputer }

constructor TComputer.Create(Cup: Integer);
begin
  writeln('constructed computer: cup = ', Cup);
end;

{ TCellPhone }

constructor TCellPhone.Create(Cup: Integer; Teapot: string);
begin
  inherited Create(Cup);
  writeln('constructed cellphone: Teapot = ', Teapot);
end;

var
  C1, C2, C3: TComputer;

begin
  C1 := TComputer.Create(1);
  Writeln;
  C2 := TCellPhone.Create(2);
  Writeln;
  C3 := TCellPhone.Create(3, 'kettle');
  Readln;
end.

with the result being:

constructed computer: cup = 1

constructed computer: cup = 2

constructed computer: cup = 3
constructed cellphone: Teapot = kettle
Muhammad Alkarouri
  • 23,884
  • 19
  • 66
  • 101
  • Nope: Method 'Create' hides virtual method of base type 'TComputer' – Ian Boyd Oct 06 '10 at 20:34
  • Should be "reintroduce" as well - this will supress the hint – Gerry Coll Oct 06 '10 at 20:37
  • @Gerry That does it. Which drives me nuts, because now reintroduce is used to **not** hide an ancestor method - when i was told 5 hours ago that it **hides** ancestor methods. (http://stackoverflow.com/questions/3874330/delphi-how-to-hide-ancestor-constructors) i have four questions going at once, and i keep going in circles. – Ian Boyd Oct 06 '10 at 20:49
  • 1
    @Ian - `reintroduce` suppresses the warning, it does nothing more, it does not hide anything. What does hide a base class method is having a method with the same name without overriding it. – Sertac Akyuz Oct 06 '10 at 21:03
  • @Ian, `reintroduce` doesn't "show" the ancestor method. The warning you cite is obviously wrong because the method *wasn't* hidden. Muhammad's example output proves that the method is visible and callable. – Rob Kennedy Oct 06 '10 at 22:04
  • 5
    Just to reinforce the earlier comments, `reintroduce` is exactly equivalent to a local form of _suppress warning W1010_. That's all it does. – Muhammad Alkarouri Oct 06 '10 at 22:11
  • @Sertac Akyuz See http://stackoverflow.com/questions/3877063/delphi-when-does-reintroduce-hide-ancestors-and-when-does-it-show-them for an example where that's not true (i.e. having a method with the same name without overriding it does not hide the base version) – Ian Boyd Oct 07 '10 at 13:44
  • @Muhhammad Alkaroui i have an example where there are ***no*** compiler warnings, but one can use `reintroduce` to hide ancestor methods. – Ian Boyd Oct 07 '10 at 13:44
  • @Ian - Hiding the base version does not mean you won't be able to call it. I think I also have trouble figuring out what it exactly means though. That's why I asked [this question](http://stackoverflow.com/questions/3878576/method-s-hides-virtual-method-of-base-type-s-whats-really-being-hidden). – Sertac Akyuz Oct 07 '10 at 14:42
3

You could create two new overloaded constructors, for example:

type
  TXmlStream = class
  private
    FFileName: string;
  public
    constructor Create(const AFileName: string); virtual;
  end;

  TXhtmlStream = class(TXmlStream)
  private
    FEncoding: TEncoding;
  public
    constructor Create(const AFileName: string); overload; override;
    constructor Create(const AFileName: string; AEncoding: TEncoding); overload; virtual;
  end;

constructor TXmlStream.Create(const AFileName: string);
begin
  inherited Create;
  FFileName := AFileName;
end;

constructor TXhtmlStream.Create(const AFileName: string);
begin
  inherited Create(AFileName);
end;

constructor TXhtmlStream.Create(const AFileName: string; AEncoding: TEncoding);
begin
  inherited Create(AFileName);
  FEncoding := AEncoding;
end;
Ondrej Kelle
  • 36,941
  • 2
  • 65
  • 128
2

Another possibility is to write a new constructor with default parameter values where the part of the signature with non-default parameters matches the original constructor in the base class:

type
  TXmlStream = class
  private
    FFileName: string;
  public
    constructor Create(const AFileName: string); virtual;
  end;

  TXhtmlStream = class(TXmlStream)
  private
    FEncoding: TEncoding;
  public
    constructor Create(const AFileName: string; AEncoding: TEncoding = encDefault); reintroduce; virtual;
  end;

constructor TXmlStream.Create(const AFileName: string);
begin
  inherited Create;
  FFileName := AFileName;
end;

constructor TXhtmlStream.Create(const AFileName: string; AEncoding: TEncoding);
begin
  inherited Create(AFileName);
  FEncoding := AEncoding;
end;
Ondrej Kelle
  • 36,941
  • 2
  • 65
  • 128
2

Also remember that constructors don't HAVE to be called Create. Older versions of Delphi didn't have method overloading, so you had to use different names:

TComputer = class(TObject) 
public 
    constructor Create(Cup: Integer); virtual; 
end; 

TCellPhone = class(TComputer) 
private
  FTeapot: string;
public 
    constructor CreateWithTeapot(Cup: Integer; Teapot: string); virtual; 
end; 

...

constructor TCellPhone.CreateWithTeapot(Cup: Integer; Teapot: string); 
begin
  Create(Cup);
  FTeapot := Teapot;
end;

Both constructors will now be available.

Gerry Coll
  • 5,867
  • 1
  • 27
  • 36