Delphi:reintroduce 关键字何时会隐藏祖先类,何时会显示它们?

7

今天 最近在Stackoverflow上我学到了以下内容:

我一直试图理解这些内容,所以这里还有一个非常具体的问题,支持我的主要问题涉及构造函数


更新:替换整个问题:

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;

构建TCellPhone时,有三种构造函数可用:

  • Cup: Integer
  • Cup: Integer; Teapot: string
  • [Teapot: String = '']

问题:为什么constructor(Teapot: string='')没有被隐藏?


现在我添加了第三个派生类:

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;

TiPhone = class(TCellPhone)
public
   constructor Create(Cup: Integer); override;
end;

在构建 TiPhone 时,有四个构造函数可用:

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

为什么会有四个构造函数?我重写了其中一个现有的三个。 编辑:这可能是代码智能提示中的一个错误,它向我显示了四个 - 但我怎么可能调用两个相同的函数。


再次使用原始代码:

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;

众所周知,TCellPhone 已经有了三个构造函数:

  • Cup: Integer
  • Cup: Integer; Teapot: string
  • [Teapot: String = '']

我该如何修改 TCellPhone 的声明以隐藏其祖先构造函数?例如:

TNokia = class(TCellPhone)
end;

只有两个构造函数:

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

现在考虑当使用 reintroduce 隐藏非虚祖先时的情况。在上一个例子中,TiPhone 有四个构造函数(理想情况下应该只有两个-TComputer 某种方式隐藏其祖先)。但即使我无法修复 TComputer,我也可以将 TiPhone 更改为仅具有一个构造函数:

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;

TiPhone = class(TCellPhone)
public
    constructor Create(Cup: Integer); reintroduce;
end;

现在 TiPhone 只有一个构造函数:

  • Cup: 整数

重新引入通常只用于抑制隐藏 虚拟 祖先的警告。在这种情况下:

Create(Teapot: string = '')

这个变量尚未被声明为虚拟的,但我仍然可以使用“reintroduce”关键字来隐藏它。


但是,如果我给TiPhone添加另一个重载方法:

TiPhone = class(TCellPhone)
public
   constructor Create(Cup: Integer); reintroduce; overload;
   constructor Create(Handle: String); overload;
end;

突然间,之前隐藏的祖先们回来了:

  • TiPhone.Create(7);
  • TiPhone.Create('pink');
  • TiPhone.Create(7, 'pink');
  • TiPhone.Create();

正如你所看到的,我很难理解以下逻辑:

  • 何时将某些内容隐藏
  • 如何隐藏某些内容
  • 何时显示某些内容
  • 如何显示某些内容

@Ian:今天你确实问了很多问题!不错! - Andreas Rejbrand
我已经使用这些定义运行了一个完整的示例。幸运的是,我们已经在下面得到了答案。 - Muhammad Alkarouri
@Rob Kennedy,如果您需要,我可以附上一个截图来修改问题? - Ian Boyd
不,Ian,我只是想确保我正确理解了你的话,因为这里的代码与[@Muhammad的示例][1]不完全相同 - 基本方法不是虚拟的,并且后代中有多个方法,其中没有一个使用reintroduce - Rob Kennedy
https://dev59.com/5nRB5IYBdhLWcg3wCjcV - Gabriel
显示剩余3条评论
4个回答

7
您不使用reintroduce来隐藏祖先类的方法。只需声明一个与祖先类中的方法同名但不覆盖或重载的方法即可实现此目的。您使用reintroduce来抑制Delphi在虚拟祖先类方法(被隐藏的方法)时引发的警告。
如果子类的方法覆盖了祖先的方法,则它不会被隐藏。祖先方法的调用将路由到子类的方法。
如果子类的方法重载了祖先的方法,则也不会被隐藏。两者都可以调用。

有一种特殊情况是在重载虚方法时。您可以使用组合的 reintroduce; overload;。这似乎与您的解释不太相符,因为仅使用 overload 会产生警告。 - Muhammad Alkarouri
关于穆罕默德所说的内容,你能否详细解释一下那种情况(添加reintroduce以隐藏祖先类中的方法)的情况,罗布? - Ian Boyd
很抱歉,@Ian。这需要我进行实验,但我没有可用的Delphi编译器。其他人将不得不枚举所有不同的方式,这些指令可以一起使用,并了解它们产生的影响(或没有影响)。 - Rob Kennedy
我将扩展问题,其中 reintroduce 隐藏了一个非虚拟祖先方法(或者这是它抑制警告的副作用)。 - Ian Boyd

1

好吧,看起来你无法在重载方法/构造函数的类中隐藏它。我想出了这个小“hack”来隐藏TComputer的构造函数。

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

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

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

  TiPhone = class(TCellPhone)
  public
    constructor Create(Cup: Integer); reintroduce; virtual;
  end;

在此示例中,TiPhone仅有1个可用构造函数。虽然这会破坏多态性(为了隐藏TC手机的第二个构造函数而付出的代价),但我想知道是否有人找到了不破坏多态性的方法。

此外,请注意,代码提示显示4个“构造函数”并不意味着确实有4个可用的构造函数。我注意到,对于每个构造函数的“覆盖”,我会在代码提示中列出1个构造函数。但在这种情况下,只会调用后代构造函数。

这个例子会抱怨TCellPhone的第二个构造函数隐藏了THackComputer中的构造函数,但我认为这是一个误报,因为TCellPhone中的构造函数重写了THackComputer的构造函数。(我想这是一个边角情况的错误,因为这不是非常常见的代码结构)


这是一个非常巧妙的技巧。我遇到了与你相同的问题(抱怨TCellPhone中的另一个构造函数隐藏了THackComputer中的构造函数)。解释为什么会出现这种情况值得提问 - 这可以作为 Delphi 构造函数的第五个问题。原因是因为我不明白它为什么会抱怨,这总是一个学习的机会。 - Ian Boyd
好吧,就像我说的那样,这可能只是一个角落案例的 bug。有些代码结构会让编译器偶尔出现“误报”警告。 - Ken Bourassa

1

如果一个方法不是虚函数,你就不能重写它,因此你没有隐藏任何东西。这就是为什么没有警告。

编辑: 我想撤回我的说法“你没有隐藏任何东西”。我想我不太明白这里的隐藏含义。我在这方面提了一个 问题

更新:
根据我得到的答案,我想重新表述我的答案:由于TComputer.Constructor未声明为虚函数,您已经从派生类中隐藏了该方法。 因此,TCellPhone构造函数无法隐藏根本没有显示的内容,因此没有编译器警告。


那是正确的答案。具体来说,TComputer的构造函数应该是虚拟的以触发警告。 - Muhammad Alkarouri
但是Sertac,Ian的意思是TComputer.Create在这种情况下并没有被隐藏。他可以调用TCellPhone.Create('foo'),代码编译并运行,创建一个TCellPhone对象。他提供了一张截图来证明它。 - Rob Kennedy
@Rob - 我在 stackoverflow 上问了一个关于编译器中“隐藏”含义的问题。这并不是指完全无法调用,而是指您将无法在 TCellPhone 的后代类中重写 TComputer.Constructor。请参见 Ken 的答案 和 Barry 的评论。 - Sertac Akyuz
@Sertac Akyuz,你无法重写TComputer.Constructor,因为它不是虚函数,而不是因为它被“隐藏”。 - Ian Boyd
@Sertac 但它既不是隐藏的,也不是虚拟的。 - Ian Boyd
显示剩余2条评论

0

我想把这个作为对Rob Kennedy答案的评论,但由于我不能,所以我来了...

同时没有关于隐藏祖先构造函数的警告。

仅仅因为你不需要。

如果我隐藏了祖先,为什么没有警告?我没有重新引入。

同样,仅仅因为你没有隐藏任何东西。

你看到了你没有隐藏任何东西的证据。你检查过的三个可用构造函数就是证明。

为什么可以使用reintroduce来隐藏祖先?

正如Rob提到的那样,reintroduce仅仅是抑制编译器提示/警告。这个单词背后没有真正的技术性。因此,你不会通过reintroduce隐藏任何东西。

我想表达一下我的隐藏思路,但我同意Sertac的观点,首先我必须知道在这种情况下你对隐藏的定义是什么。

编辑: 我刚刚读了你提到的帖子,我认为你误解了概念。这里是我的简短解释。

reintroduce被用来隐藏祖先构造函数

那篇帖子的答案并没有表明这一点。真正隐藏祖先构造函数的是具有与祖先相同参数的后代的新构造函数。关键字 reintroduce 只是抑制编译器警告。

reintroduce 用于显示祖先构造函数

在该帖子的答案中,是重载关键字使得祖先的构造函数仍然可用。

针对Ian在下面的评论中提出的问题做出补充:

解决你困惑的第一步是确定实际问题。如果我们仔细检查你的帖子,显然你想要一步解决两个问题。这两个问题是:

  1. 隐藏具有特定名称的祖先构造函数
  2. 在后代中有多个具有相同特定名称的构造函数。

虽然它们看起来是简单的问题,但仔细检查会立即让你意识到它们的本质完全相反。问题1想要隐藏一个方法/构造函数,而问题2想要展示不止一个,而是多个方法/构造函数。因此,如果你在一步中混合它们,它们肯定会互相抵消。难怪它们让你头疼... :)

解决这两个问题的基本规则是不要在一步中混合它们。这意味着我们需要一个中间类来解决问题1,并在该中间类的后代中进行重载。就像这样:

type
  TComputer = class(TObject)
  private
    FCaller: TConstructorCaller;
  public
     constructor Create(Teapot: string=''); virtual;

     property Caller: TConstructorCaller read FCaller;
  end;

  TBaseCellphone=class(TComputer)
    constructor Create(Cup: Integer); virtual;
  end;

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

  TiPhone = class(TCellPhone)
  public
    constructor Create(Cup: Integer); reintroduce; overload;
    constructor Create(Handle: String); reintroduce; overload;
  end;

从上面的代码可以看出,TBaseCellphone是中介类。在这种情况下,它的任务仅仅是隐藏TComputer的构造函数Create。请注意,您不能在此处使用overload关键字,否则隐藏将被取消。现在,在隐藏完成后,您可以在其子类中自由地使用overload关键字来获得具有相同名称的多个构造函数。
要检查,请查看以下代码将无法编译:
   TCellPhone.Create('My Teapot');

我认为我在Delphi中找到了错误。 overload关键字使得祖先的构造函数仍然可用 - 而这不是它的本意。它的本意是允许在TCellPhone中有两个重载。如何使用overload来允许多个重载,但不允许从祖先继承的方法可见?换句话说:如何隐藏祖先的构造函数? - Ian Boyd
@Ian - 这不是一个错误。重载方法“……如果重新声明的方法和祖先方法有不同的参数签名,它会重载继承的方法而不是隐藏它。” - Sertac Akyuz
Ian,重载允许您拥有具有相同名称的多个例程或方法。它们只需要具有不同的参数即可。关于您的问题,我会看看能否提供一个好的例子。 - Luthfi

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