“Method '%s' hides virtual method of base type '%s'” 的意思是什么?实际上隐藏了什么?

7

在阅读完Ian Boyd的构造函数系列问题(1, 2, 3, 4)之后,我意识到我并没有完全理解被隐藏的真正含义。

我知道(如果我有错请指出),override的唯一目的是能够具有多态行为,这样运行时可以根据实例的实际类型而不是声明类型来解析方法。考虑以下代码:

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.

输出结果为:

Base.Proc1
Child.Proc1

Base.Proc2

关于TChild.Proc2的警告说明了该方法“将隐藏对基类同名方法的访问”。但是我看到的是,如果我没有覆盖Proc2,我就失去了将方法解析为其实际类型而不是其基类型的能力。那么这是如何隐藏基类方法的访问呢?

此外,在解决警告的文档中,它指出:

首先,您可以指定override来使派生类的过程也是虚拟的,从而允许继承的调用仍然引用原始过程。

现在,如果我从'TChild'创建一个'TChild'实例(无多态性),非重载方法中的继承调用显然会引用原始过程。如果我从'TBase'创建'Child'实例,则调用甚至不解析为'TChild'方法,我怎么调用'Inherited'以引用任何东西呢?

我有什么误解吗?

3个回答

5

除其他外,您将无法定义

在技术方面的含义。
TGrandChild = class(TChild) 
  procedure Proc2; override;
end; 

因为TGrandChild看到的Proc2是来自非虚拟的TChild。TChild.Proc2 隐藏了从TBase继承过来的Proc2,使其对子孙类不可见。

编辑:

回答Sertac的评论:

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

  Base.Free; 
  Readln; 

那将输出:
Base.Proc2
Base.Proc2
Child.Proc2

所以,看起来调用同一个方法两次,实际上是调用了两个不同的方法。这使得代码更难理解(这并不实用),并产生意外的行为。


那是错误的。移除override关键字,从TBase创建一个TGrandChild实例,并在其上调用Proc2方法。这个调用将会解析为TBase.Proc2。 - Sertac Akyuz
1
@Sertac - Ken 是正确的。警告所警告的是无法从 TChild 的后代中覆盖 TBase.Proc2。Ken 编写的代码无法编译,因为 TChild.Proc2 不是虚拟的。显然,TBase.Proc2 旨在被覆盖,但它无法被覆盖,因为它已被 TChild.Proc2 隐藏。 - Barry Kelly
@Ken - 我想我对警告短语的理解有问题。实际上,我从来没有考虑过可能存在的子类,谢谢你的提醒。不过,我也很想知道“继承调用引用原始过程”的含义。什么情况下它们不会引用? - Sertac Akyuz
我觉得你应该去阅读关于多态性的相关内容,以了解它的工作原理。至于答案,声明一个具有非重写Proc函数的TGrandChild不会隐藏TChild.Proc在TBase.Proc中的影响。除了共享名称之外,TGrandChild.Proc与TChild.Proc和TBase.Proc没有任何关系。 - Ken Bourassa
@Ken - 谢谢你的回答,我不知道你已经回复了我的评论,当我删除它时,对此表示抱歉。 - Sertac Akyuz
显示剩余13条评论

2
你想得太复杂了。隐藏并不意味着你完全失去对原始内容的访问权。这只是意味着(而且你已经自己指出了)如果你有一个静态类型为TChild的对象,并在其上调用Proc2,它将调用TChild中的函数,而不是TBase中的函数。同时,Ken所说的也是正确的。
它发出警告是因为原始内容是虚拟的,而隐藏很可能不是人们编写此类代码时的意图。至少这是糟糕的编码风格。

谢谢Timo,的确我认为隐藏意味着失去了访问基类方法的权限...我不明白你下一句话的意思。在声明为“TChild”的“Child”上调用“Proc2”当然会运行“TChild.Proc2”。这与“覆盖”/“隐藏”有关吗? - Sertac Akyuz
抱歉,我的上一条评论有误。通常,我会这样定义隐藏:在某个作用域中有一个标识符,你声明该标识符表示其他意义。例如,你可以有一个全局变量x,然后声明一个本地变量x来隐藏全局变量。这里也类似。考虑像“child.Proc2;”这样的代码,其中child是TChild。如果Proc2在TChild中未重新声明,则调用TBase.Proc2。但由于它被重新声明了,所以调用TChild.Proc2。 - Timo

2
使用'reintroduce'来抑制警告。

3
欢迎来到 Stack Overflow。你的参与热情真是鼓舞人心。但下次请记得在回答前先阅读问题。Sertac 并不是在问如何去掉警告。 - Rob Kennedy
为什么 reintroduce 可以抑制警告? - Ian Boyd
1
@Ian 因为有时这是期望的行为。reintroduce 的整个目的就是抑制警告;reintroduce 不会做其他任何事情。 - Barry Kelly

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接