views:

136

answers:

3

After having read Ian Boyd's constructor series questions (1, 2, 3, 4), I realize I don't quite grasp the literal meaning on what's being hidden.

I know (correct me if I'm wrong) override's sole purpose is to be able to have polymorphic behavior, so that run-time can resolve a method depending on the actual type of an instance - as opposed to the declared type. Consider the following code:

type
  TBase = class
    procedure Proc1; virtual;
    procedure Proc2; virtual;
  end;

  TChild = class(TBase)
    procedure Proc1; override;
    procedure Proc2;            // <- [DCC Warning]
  end;

procedure TBase.Proc1;
begin
  Writeln('Base.Proc1');
end;
procedure TBase.Proc2;
begin
  Writeln('Base.Proc2');
end;

procedure TChild.Proc1;
begin
  inherited Proc1;
  Writeln('Child.Proc1');
end;
procedure TChild.Proc2;
begin
  inherited Proc2;
  Writeln('Child.Proc2');
end;

var
  Base: TBase;
begin
  Base := TChild.Create;
  Base.Proc1;
  Writeln;
  Base.Proc2;
  Base.Free;
  Readln;
end.

Which outputs:

Base.Proc1
Child.Proc1

Base.Proc2

The warning on TChild.Proc2 states that this method "will hide access to the base's method of the same name". What I see is, if I don't override Proc2 I loose the ability of the method's resolving to its actual type, not of its base type. How's that hiding access to base's method?

Further, down the documentation on the warning as a solution to the warning, it is stated that:

First, you could specify override to make the derived class' procedure also virtual, and thus allowing inherited calls to still reference the original procedure.

Now, if I create a 'TChild' instance from a 'TChild' (no polymorphism), the inherited call in the non-overridden method clearly refers to the original procedure. If I create the 'Child' instance from a 'TBase', the call does not even resolve to a 'TChild' method, how could I call 'Inherited' that would refer to anything at all?

What am I misunderstanding?

+3  A: 

Amongs other thing, you won't be able to define

TGrandChild = class(TChild) 
  procedure Proc2; override;
end; 

because Proc2 that TGrandChild sees is the one from TChild that is not virtual. The TChild.Proc2 hide TBase.Proc2 from descendants.

EDIT:

In answer to Sertac's comment:

var 
  Base: TBase; 
  Child : TChild
begin 
  Child := TChild.Create;
  Base := Child;
  Base.Proc2; 
  Child.Proc2;

  Base.Free; 
  Readln; 

That will output

Base.Proc2
Base.Proc2
Child.Proc2

So, what seems to be a call to the same method twice is actually a call to 2 different methods. That makes code harder to understand (which is not practical) and yield unexpected behavior.

Ken Bourassa
That's wrong. Remove the override, create a 'TGrandChild' instance from a 'TBase', and call 'Proc2' on it. The call would resolve to 'TBase.Proc2'.
Sertac Akyuz
In the previous comment I meant to write "the override directive" instead of "the override". Wouldn't make a difference on the result though..
Sertac Akyuz
@Sertac - Ken is correct. The thing the warning is warning you about is the *inability* to override TBase.Proc2 from descendants of TChild. The code Ken wrote won't compile, because TChild.Proc2 is not virtual. Obviously, it was intended for TBase.Proc2 to be overridden, but it can't be overridden, because it has been hidden by TChild.Proc2.
Barry Kelly
Thanks @Barry. From now on whenever I see the warning, I'll read it: *"Method '%s' hides virtuality of method of base type '%s' from descendant classes"* if that's OK.
Sertac Akyuz
@Ken - I guess I had a problem with the meaning I made out from the warning phrase. Actually I had never though about possible descendant classes, thanks for the view. Still, I'd also appreciate an explanation about what's meant by *"inherited calls referencing the original procedure"*. When do they don't?
Sertac Akyuz
@Sertac it doesn't "hide the virtuality". TChild.Proc2 is a different method to TBase.Proc2. It's not the virtuality that's being hidden; it's entire method.
Barry Kelly
@Barry - Sorry for being a thick head, I understand descendants would not be able to override the base method, that's why I thought (only) 'virtuality' was being terminated. Can you tell the practical significance of being a different method (other than being able to override in descendants)?
Sertac Akyuz
@Sertac - 1 thing you might need to understand is, the warning messages assume you named the TChild.Proc2 the same as TBase.Proc2 "by accident". It's the way the compiler tells you "Hey! There is already a method named that way!". If it is "by design" that you hide the method, you need to flag it with "reintroduce" to suppress the warning. As for the practical significance, you already showed it in your initial post. I'll edit my answer to add more significant exemple.
Ken Bourassa
@Ken - I know the reason of the warning and I know the consequence. I also know reintroduce. What I don't know is what does 'TBase.Proc2' being hidden means. In your code and my code it is in fact the one which runs when 'TChild' does not override 'Proc2'. When Barry said (it) *"is the inability to override TBase.Proc2 from descendants of TChild"* I thought I understood. But when he says *"'TChild.Proc2' is an entirely different method which hides 'TBase.Proc2'"* I loose it again. 'TBase.Proc2' is there, I can call it, 'TChild.Proc2' is an entirely different method. How's 'TBase.Proc2' hidden?
Sertac Akyuz
You cannot call TBase.Proc2 from an object reference of type TChild. The only way you could do that is by typecasting it. (i.e. TBase(Child).Proc2). Calling Child.Proc2 would call the TChild.Proc2. Calling "Inherited" from TChild.Proc2 and not overriding the method is bad programming IMO, unless you want to intentionnaly break the polymorphism. So, from a TChild object reference, TBase.Proc2 IS hidden. (You cannot call it by name). It still has an entry in the object's VMT, but you can only call the static method TChild.Proc2.
Ken Bourassa
@Ken - > What I was referring was the inherited call - of course in the example I posted breaking polymorphism is intentional, why would it omit 'override' otherwise?.. Besides, how would calling 'TBase.Proc2' would be different if 'TChild' overrided it?
Sertac Akyuz
Calling TBase(Child).Proc2 would still call TChild.Proc2 if it was overridden. As for the warning... The warning is there for people who mistakenly name a procedure that an ancestor already has one named the same. If breaking polymorphism is intentional, you should use "Reintroduce".
Ken Bourassa
@Ken - Meaning, it's the same call either it's overriden or not. .. If I had used 'reintroduce' how could I ask about the compiler warning? ;)
Sertac Akyuz
If you override, you call a virtual method. So the compiler will generate code to call the GetDynaMethod routine to find the address of the right method to call. If you don't, the compiler already know the address of the right method to call and just call it, as the method is static. So, no, the call is not the same.
Ken Bourassa
@Ken - Oh, I meant 'same call' from user code. I don't think the "hides" in the warning would be referring to compiler implementation details. There's something user code cannot do if he does not override the base method (other than terminating the virtual chain and breaking polymorphism), and I don't understand what it is.
Sertac Akyuz
@Sertac - yes, descendants can't override the method because it has been hidden. When you do foo.Method, the compiler looks up Method on the static type of foo, and its ancestors, until it finds a definition. At runtime, it calls it; if it's virtual, it will end up calling the most derived definition (override) of that method. Hiding the base method (a) prevents ancestors from overriding the method, and (b) stops them from being the most derived definition (override) of that method. This is because it's hidden. But I can see you clearly don't *want* to understand, I'll stop informing you.
Barry Kelly
@Barry - Thanks.
Sertac Akyuz
I think you should go read quite a bit on polymorphism to understand how it works. As for the answer, declaring a TGrandChild with a non overridden Proc function won't hide TChild.Proc from TBase.Proc. TGrandChild.Proc is totally unrelated to TChild.Proc and TBase.Proc beside sharing the name.
Ken Bourassa
@Ken - Thanks for the answer, I wasn't aware you have answered my comment when I deleted it, sorry for that.
Sertac Akyuz
+2  A: 

You are thinking too complicated. Hiding doesn't mean you completely lose access to the original. It simply means (and you already noted this yourself) if you have an object of static type TChild, and you call Proc2 on it, it calls the one in TChild, and not the one in TBase. Also what Ken said is true.

It is warning you because the original is virtual and hiding is most likely not what people intend when writing code like that. At the very least it's bad coding style.

Timo
Thanks Timo, indeed I thought hiding would mean losing access to base class' method... I don't follow what you mean with your next statement. Calling 'Proc2' on a 'Child' declared as a 'TChild', would of course run 'TChild.Proc2'. Is this relevant with 'overriding'/'hiding'?
Sertac Akyuz
oops, nevermind
Timo
Sry, messed up with my last comment. In general, I would define hiding like this: you have an identifier in a certain scope, and you declare that identifier to mean something else. For example you could have a global variable x, and then you declare a local variable x that hides the global. It's similar here. Consider a code like "child.Proc2;" where child is TChild. If Proc2 is not redeclared in TChild then it calls TBase.Proc2. But because it is redeclared, it calls TChild.Proc2.
Timo
A: 

Use 'reintroduce' to suppress the warning.

dwrbudr
Welcome to Stack Overflow. Your eagerness to participate is inspiring. But next time, please remember to read the question before answering. Sertac did not ask how to get rid of the warning.
Rob Kennedy
Why does `reintroduce` suppress the warning?
Ian Boyd
@Ian Because sometimes it's desired behaviour. The entire purpose of `reintroduce` is to suppress the warning; `reintroduce` does nothing else.
Barry Kelly