Delphi: How to hide ancestor constructors?
Asked Answered
K

6

13

Update: gutted the question with a simpler example, that isn't answered by the originally accepted answer

Given the following class, and its ancestor:

TComputer = class(TObject)
public
   constructor Create(Teapot: string='');
end;

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

Right now TCellPhone has 3 constructors visible:

  • Cup: Integer
  • Cup: Integer; Teapot: string
  • Teapot: string = ''

What do i do to TCellPhone so that the ancestor constructor (Teapot: string = '') is not visible, leaving only the declared constructors:

  • Cup: Integer
  • Cup: Integer; Teapot: string

Note: Usually the simple act of having a descendant constructor hides the ancestor:

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

And if you wanted to keep both the ancestor constructor and the descendant, you would mark the descendant as an overload:

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

In this question's example code, Delphi is mistaking my overload keywords:

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

to think that:

  • i want to overload my constructors with the ancestor,
  • when really i want to overload it with the sibling

How do i hide the ancestor constructor?

Note: It might be impossible to hide the ancestor, non-virtual, constructor using the Delphi language as it is currently defined. "Not possible" is a valid answer.


Attempted Answer (failed)

i tried marking the descendant constructors with reintroduce (falling back to my mode of randomly adding keywords until it works):

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

But that didn't work, all three constructors are still visible. :(


Original Question

i have an object that descends from a class that has constructors don't want to see:

TEniac = class(TObject)
   constructor Create(PowerCord: TPowerCord=nil); //calls inherited Create

TComputer = class(TEniac) ...
   constructor Create(PowerCord: TPowerCord=nil); //calls inherited Create(nil)

TCellPhone = class(TComputer)
   constructor Create(sim: TSimChip; UnlockCode: Integer); //calls inherited Create(nil)

TiPhone = class(TCellPhone)
   constructor Create(sim: TSimChip); //calls inherited Create(sim, 0)

Note: This is a hypothetical example. As in the real world, the ancestor objects cannot be changed without breaking existing code.

Now when someone's using TiPhone i don't want them even being able to see the constructor from TEniac:

iphone := TiPhone.Create(powerCord);

Worse still: if they call that constructor, they completely miss my constructor, and everything done in between. It's pretty easy to call the wrong constructor, all of them are visible in the IDE code-completion, and will compile:

TiPhone.Create;

and they get a completely invalid object.

i could change TCellPhone to throw an exception in those constructors:

TCellPhone.Create(PowerCord: TPowercord)
begin
   raise Exception.Create('Don''t use.');
end;

But developers won't realize they're calling the wrong constructor until the customer finds the error one day and fines us bazillions of dollars. In fact, i'm trying to find everywhere i call the wrong constructor - but i can't figure out how to make Delphi tell me!

Kopaz answered 6/10, 2010 at 15:46 Comment(3)
"hypothetical example" i'm afraid you're going to have to follow the concepts along in your brain, rather than in an IDE.Kopaz
I guess this question is too hypothetical for me, then. :-)Kendre
@Ulrich Gerhardt i could write the dozens of lines of code to come up with a compilable example - but that misses the point of readable code in the question. Questions 4 or 5 pages long and people simply stop reading; or worse: skim.Kopaz
T
8

It's impossible to ever make a constructors introduced in an ancestor inaccessible for the creation of a derived class in Delphi because you can always do this:

type
  TComputerClass = class of TComputer;

var
  CellPhoneClass: TComputerClass = TCellPhone;
  CellPhone : TCellPhone;
begin
  CellPhone := CellPhoneClass.Create('FUBAR') as TCellPhone;
end;

Nothing you could do in the code of any derived class would ever be able to prevent anyone from calling the TComputer.Create constructor for creating an instance of the derived class.

The best you could do is:

TComputer = class(TObject)
public
   constructor Create(Teapot: string=''); virtual;
end;

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

In that case the code above would at least be calling TCellPhone.Create(Teapot: string='') instead of TComputer.Create(Teapot: string='')

Transpierce answered 2/11, 2010 at 5:0 Comment(0)
M
8

If I remember correctly, then reintroduce should help for virtual methods.

The reintroduce directive suppresses compiler warnings about hiding previously declared virtual methods. Use reintroduce when you want to hide an inherited virtual method with a new one.

To answer your updated question - I think it's not possbile to hide a non-virtual constructor with overloading in a directly derived class, but I tried the following successfully:

TComputer = class(TObject)
public
  constructor Create(Teapot: string='');
end;

TIndermediateComputer = class(TComputer)
protected
  // hide the constructor
  constructor Create;
end;

TCellPhone = class(TIndermediateComputer)
public
   constructor Create(Cup: Integer); overload; virtual;
   constructor Create(Cup: Integer; Teapot: string); overload; virtual;
end;
Mavis answered 6/10, 2010 at 15:52 Comment(3)
Reintroduce may suppress the compiler warnings about hiding an ancestor method, but how does it help in identifying the use of the constructors that shouldn't be used at compile time? Instead of just throwing an exception at run time, when the customers may sue poor Ian for all he's got?Spanishamerican
@Marjan Venema In this case reintroduce isn't being used for it's documented purpose; there are no warnings about hiding ancestor methods (it turns out that you don't see that warning if the ancestor methods are not virtual). In this case i can use reintroduce for it's secret ability to hide ancestor methods.Kopaz
@Ian. Oh, dear, brain went to idle it seems. Ok, so no compile time messages, but when re-implementing the constructors you can either throw an exception or call the correct constructor with a default parameter...Spanishamerican
T
8

It's impossible to ever make a constructors introduced in an ancestor inaccessible for the creation of a derived class in Delphi because you can always do this:

type
  TComputerClass = class of TComputer;

var
  CellPhoneClass: TComputerClass = TCellPhone;
  CellPhone : TCellPhone;
begin
  CellPhone := CellPhoneClass.Create('FUBAR') as TCellPhone;
end;

Nothing you could do in the code of any derived class would ever be able to prevent anyone from calling the TComputer.Create constructor for creating an instance of the derived class.

The best you could do is:

TComputer = class(TObject)
public
   constructor Create(Teapot: string=''); virtual;
end;

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

In that case the code above would at least be calling TCellPhone.Create(Teapot: string='') instead of TComputer.Create(Teapot: string='')

Transpierce answered 2/11, 2010 at 5:0 Comment(0)
S
5

Instead of only raising an "Don't use" exception in the overridden invalid constructors, consider marking them deprecated in the class where they become invalid. That should produce nice compiler warnings when these invalid constructors are used erroneosly.

TCellPhone = class(TComputer)
   constructor Create(PowerCord: TPowerCord=nil); deprecated;
   constructor Create(sim: TSimChip; UnlockCode: Integer); //calls inherited Create(nil)

In addition, use override or reintroduce as needed.

Spanishamerican answered 6/10, 2010 at 18:43 Comment(3)
i would love to go back in time and add deprecated keyword to Delphi 5. i'll add that to my list, right after telling Copernicus that the planets follow and elliptical orbit.Kopaz
Imagine how much more advanced humanity could be if i could go back in time and give people the right ideas at the right time.Kopaz
@Ian: ah yes, but then again: wouldn't that take much of the fun out of the journey of discovery? :-)Spanishamerican
H
4

You can't hide the parent class' constructor unless it was declared virtual or dynamic. You can however prevent it from being called from the child class. Consider your example:

TComputer = class(TObject)
public
   constructor Create(Teapot: string='');
end;

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

TComputer.Create will always be visible from TCellPhone. You can prevent TComputer.Create from being inadvertantly called by declaring a TCellPhone.Create with the same signature.

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

Then as long as you don't have a call to inherited in the body of TCellPhone.Create(Teapot: string='') you can prevent TComputer.Create from being called in TCellPhone and its descendants. The following:

TCellphone.Create;
TCellphone.Create('MyPhone');

Will resolve to TCellPhone's implementation.

Additionally:

TiPhone = class(TCellPhone)
    constructor Create;
end;

constructor TiPhone.Create;
begin
  inherited;
end;

Will invoke TCellPhone.Create and not TComputer.Create.

Hardin answered 8/10, 2010 at 4:39 Comment(2)
"You can't hide the parent class' constructor unless it was declared virtual or dynamic." This isn't strictly true. You can hide the parent class's constructor, even though it is not virtual/dynamic, simply by declaring a new constructor.Kopaz
@Ian but as you found out when you overloaded the constructor in the child class the parent class' constructor became visible again. Using my suggestion will effectively block the parent constructor from being called from the child.Hardin
T
1

You want to reintroduce the constructor:

TiPhone = class(TCellPhone)
    constructor Create(sim: TSimChip); reintroduce;

See TComponent.Create in the Delphi source code for a real-world example of this.

Tragedy answered 6/10, 2010 at 15:53 Comment(0)
O
1

I know that this is 5 years old topic, but still it may help someone. The only way to hide the ancestor's constructor is to rename one of the two Create methods to something else and remove the need of overload directive. It looks weird but it's the only way. At least in older versions of Delphi. I don't know is it possible now in the XE xxx versions.

Olander answered 30/11, 2015 at 13:49 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.