I see two reasons your original set of declarations shouldn’t compile cleanly:
There should be a warning in
TCellPhonethat its constructor hides the method of the base class. This is because the base-class method is virtual, and the compiler worries that you’re introducing a new method with the same name without overriding the base-class method. It doesn’t matter that the signatures differ. If your intention is indeed to hide the method of the base class, then you need to use
reintroduceon the descendant declaration, as one of your blind guesses showed. The sole purpose of that directive is to quell the warning; it has no effect on run-time behavior.
Ignoring what’s going to happen with
TIPhonelater on, the following
TCellPhonedeclaration is what you’d want. It hides the ancestor method, but you want it to be virtual as well. It won’t inherit the virtualness of the ancestor method because they’re two completely separate methods that just happen to have the same name. Therefore, you need to use
virtualon the new declaration as well.
TCellPhone = class(TComputer) public constructor Create(Cup: Integer; Teapot: string); reintroduce; virtual; end;
The base-class constructor,
TComputer.Create, is also hiding a method of its ancestor,
TObject.Create, but since the method in
TObjectis not virtual, the compiler doesn’t warn about it. Hiding non-virtual methods happens all the time and is generally unremarkable.
You should get an error in
TIPhonebecause there is no longer any one-argument constructor to override. You hid it in
TCellPhone. Since you want to have two constructors,
reintroduceclearly wasn’t the right choice to use earlier. You don’t want to hide the base-class constructor; you want to augment it with another constructor.
Since you want both constructors to have the same name, you need to use the
overloaddirective. That directive needs to be used on
all the original declarations — the first time each distinct signature is introducedsubsequent declarations in descendants. I thought it was required on all declarations (even the base class), and it doesn’t hurt to do that, but I guess it’s not required. So, your declarations should look like this:
TComputer = class(TObject) public constructor Create(Cup: Integer); overload; // Allow descendants to add more constructors named Create. virtual; // Allow descendants to re-implement this constructor. end; TCellPhone = class(TComputer) public constructor Create(Cup: Integer; Teapot: string); overload; // Add another method named Create. virtual; // Allow descendants to re-implement this constructor. end; TiPhone = class(TCellPhone) public constructor Create(Cup: Integer); override; // Re-implement the ancestor's Create(Integer). constructor Create(Cup: Integer; Teapot: string); override; // Re-implement the ancestor's Create(Integer, string). end;
Modern documentation tells what order everything should go in:
reintroduce; overload; binding; calling convention; abstract; warning
where binding is virtual, dynamic, or override; calling convention is register, pascal, cdecl, stdcall, or safecall; and warning is platform, deprecated, or library.
Those are six different categories, but in my experience, it’s rare to have more than three on any declaration. (For example, functions that need calling conventions specified probably aren’t methods, so they can’t be virtual.) I never remember the order; I’ve never seen it documented till today. Instead, I think it’s more helpful to remember each directive’s purpose. When you remember which directives you need for different tasks, you’ll end up with just two or three, and then it’s pretty simple to experiment to get a valid order. The compiler might accept multiple orders, but don’t worry — order isn’t important in determining meaning. Any ordering the compiler accepts will have the same meaning as any other (except for calling conventions; if you mention more than one of those, only the last one counts, so don’t do that).
So, then you just have to remember the purpose of each directive, and think about which ones don’t make any sense together. For example, you cannot use
override at the same time because they have opposite meanings. And you can’t use
override together because one implies the other.
If you have lots of directives piling up, you can always cut
overload out of the picture while you work out the rest of the directives you need. Give your methods different names, figure out which of the other directives they need by themselves, and then add
overload back while you give them all the same names again.