views:

3804

answers:

8

Does anyone know what the motivation was for having the reintroduce keyword in Delphi?

If you have a child class that contains a function with the same name as a virtual function in the parent class and it is not declared with the override modifier then it is a compile error. Adding the reintroduce modifier in such situations fixes the error but I have never grasped the reasoning for the compile error.

+1  A: 

When the ancestor class also has a method with the same name, and it is not necessarily declared virtual, you would see a compiler warning (as you would hide this method).

In other words: You tell the compiler that you know that you hide the ancestor function and replace it with this new function and do so deliberately.

And why would you do this? If the method is virtual in the parent class, the only reason is to prevent polymorphism. Other then that just override and do not call inherited. But if the parent method is not declared virtual (and you cannot change that, because you do not own the code for example), you can inherit from that class and let people inherit from your class without seeing a compiler warning.

Ralph Rickenbach
Could you clarify what is meant by "as you would hide this method and not be able to call inherited"?
Frank
Bet there are good reason to reintroduce instead of overriding.Your parent class might make calls to the virtual method, but you don't want these calls to get to your reintroduced method.
mliesen
+14  A: 
Jim McKeeth
You have told me what the reintroduce keyword does. Basically you said it means a function is not virtual. Yes, but so does not adding any virtual/overide/dynamic modifier to a function. But why did Anders Hejlsberg decide it was necessary to add the reintroduce keyword to the language?
Frank
It's nice to have to compiler stear you away from potential bugs. But it could just as well have been reported as a "warning" instread of an error.
mliesen
+2  A: 

The RTL uses reintroduce to hide inherited constructors. For example, TComponent has a constructor which takes one argument. But, TObject has a parameterless constructor. The RTL would like you to use only TComponent's one-argument constructor, and not the parameterless constructor inherited from TObject when instantiating a new TComponent. So it uses reintroduce to hide the inherited constructor. In this way, reintroduce is a little bit like declaring a parameterless constructor as private in C#.

Craig Stuntz
+1  A: 

Reintroduce tells the compiler you want to call the code defined in this method as an entry point for this class and its descendants, regardless of other methods with the same name in the ancestors’ chain.

Creating a TDescendant.MyMethod would create a potential confusion for the TDescendants in adding another method with the same name, which the compiler warns you about.
Reintroduce disambiguates that and tells the compiler you know which one to use.
ADescendant.MyMethod calls the TDescendant one, (ADescendant as TAncestor).MyMethod calls the TAncestor one. Always! No confusion…. Compiler happy!

This is true whether you want the descendant method to be virtual or not: in both cases you want to break the natural linkage of the virtual chain. And it does not prevent you from calling the inherited code from within the new method.

  1. TDescendant.MyMethod is virtual: ...but you cannot or don’t want to use the linkage.
    • You cannot because the method signature is different. You have no other choice as overriding is impossible in this case with return type or parameters not exactly the same.
    • You want to restart an inheritance tree from this class.
  2. TDescendant.MyMethod is not virtual: You turn MyMethod into a static one at the TDescendant level and prevent further overriding. All classes inheriting from TDescendant will use the TDescendant implementation.
François
+5  A: 

There are lots of answers here about why a compiler that lets you hide a member function silently is a bad idea. But no modern compiler silently hides member functions. Even in C++, where it's allowed to do so, there's always a warning about it, and that ought to be enough.

So why require "reintroduce"? The main reason is that this is the sort of bug that can actually appear by accident, when you're not looking at compiler warnings anymore. For example, let's say you're inheriting from TComponent, and the Delphi designers add a new virtual function to TComponent. The bad news is your derived component, which you wrote five years ago and distributed to others, already has a function with that name.

If the compiler just accepted that situation, some end user might recompile your component, ignore the warning. Strange things would happen, and you would get blamed. This requires them to explicitly accept that the function is not the same function.

apenwarr
+1  A: 

The purpose of the reintroduce modifier is to prevent against a common logical error.

I will assume that it is common knowledge how the reintroduce keyword fixes the warning and will explain why the warning is generated and why the keyword is included in the language. Consider the delphi code below;

TParent = Class
Public
    Procedure Procedure1(I : Integer); Virtual;
    Procedure Procedure2(I : Integer);
    Procedure Procedure3(I : Integer); Virtual;
End;

TChild = Class(TParent)
Public
    Procedure Procedure1(I : Integer);
    Procedure Procedure2(I : Integer);
    Procedure Procedure3(I : Integer); Override;
    Procedure Setup(I : Integer);
End;

procedure TParent.Procedure1(I: Integer);
begin
    WriteLn('TParent.Procedure1');
end;

procedure TParent.Procedure2(I: Integer);
begin
    WriteLn('TParent.Procedure2');
end;

procedure TChild.Procedure1(I: Integer);
begin
    WriteLn('TChild.Procedure1');
end;

procedure TChild.Procedure2(I: Integer);
begin
    WriteLn('TChild.Procedure2');
end;

procedure TChild.Setup(I : Integer);
begin
    WriteLn('TChild.Setup');
end;

Procedure Test;
Var
    Child : TChild;
    Parent : TParent;
Begin
    Child := TChild.Create;
    Child.Procedure1(1); // outputs TChild.Procedure1
    Child.Procedure2(1); // outputs TChild.Procedure2

    Parent := Child;
    Parent.Procedure1(1); // outputs TParent.Procedure1
    Parent.Procedure2(1); // outputs TParent.Procedure2
End;

Given the above code both of the procedures in TParent are hidden. To say they are hidden means that the procedures can not be called through the TChild pointer. Compiling the code sample produces a single warning;

[DCC Warning] Project9.dpr(19): W1010 Method 'Procedure1' hides virtual method of base type 'TParent'

Why only a warning for the virtual function and not the other? Both are hidden.

A virtue of Delphi is that library designers are able to release new versions without fear of breaking the logic of existing client code. This contrasts to Java where adding new functions to a parent class in a library is fraught with danger because classes are implicitly virtual. Lets say that TParent from above lives in a 3rd party library, and the library manufacture releases the new version below.

// version 2.0
TParent = Class
Public
    Procedure Procedure1(I : Integer); Virtual;
    Procedure Procedure2(I : Integer);
    Procedure Procedure3(I : Integer); Virtual;
    Procedure Setup(I : Integer); Virtual;
End;

procedure TParent.Setup(I: Integer);
begin
    // important code
end;

Imagine we had the following code in our client code

Procedure TestClient;
Var
    Child : TChild;
Begin
    Child := TChild.Create;
    Child.Setup;
End;

For the client it does not matter if the code is compiled against version 2 or 1 of the library, in both cases TChild.Setup is called as the user intends. And in the library;

// library version 2.0
Procedure TestLibrary(Parent : TParent);
Begin
    Parent.Setup;
End;

If TestLibrary is called with a TChild parameter, everything works as intended. The library designer have no knowledge of the TChild.Setup, and in Delphi this does not cause them any harm. The call above correctly resolves to TParent.Setup.

What would happen in a equivalent situation in Java? TestClient would work correctly as intended. TestLibrary would not. In Java all functions are assumed virtual. The Parent.Setup would resolve to TChild.Setup, but remember when TChild.Setup was written they had no knowledge of the future TParent.Setup, so they are certainly not going to ever call inherited. So if the library designer intended TParent.Setup to be called it will not be, no matter what they do. And certainly this could be catasrophic.

So the object model in Delphi requires explicit declaration of virtual functions down the chain of child classes. A side effect of this is that it is easy to forget to add the override modifier on child methods. The existence of the Reintroduce keyword is a convenience to the programmer. Delphi was designed so that the programmer is gently persuaded, by the generation of a warning, to explicitly state their intentions in such situations.

Frank
You have succinctly answered your own question superbly, but... Why is reintroduce a keyword and not a compiler option when it's usefulness is so minute?
Peter Turner
Because when it *is* useful it's very useful. Keeps certain things from getting badly b0rked on accident.
Mason Wheeler
A: 

First of all, "reintroduce" breaks the inheritance chain and should not be used, and I mean never ever. In my entire time I worked with Delphi (ca 10 years) I've stumbled upon a number of places that do use this keyword and it has always been a mistake in the design.

With that in mind here's the simplest way it works:

  1. You have like a virtual method in a base class
  2. Now you wanna have a method that has the exact same name, but maybe a different signature. So you write your method in the derived class with the same name and it will not compile because the contract is not fulfilled.
  3. You put the reintroduce keyword in there and your base class does not know about your brand new implementation and you can use it only when accessing your object from a directly specified instance type. What that means is toy can't just assign the object to a variable of base type and call that method because it's not there with the broken contract.

Like I said it's pure evil and must be avoided at all cost (well, that's my opinion at least). It's like using goto - just a terrible style :D

Matthias Hryniszak
A: 

One more thing about compiler warnings - just a reflection from reading Frank's post.

If you see a warning coming out of the compiler you did something wrong. There's a reason other compilers (like C, C++, Java or C# just to name a few important ones) allow you to treat warnings as errors. My suggestion is: if you see a warning you go nowhere without making it go away.

Matthias Hryniszak