【问题标题】:Compiler allows call to protected method in sibling class, but calls base class编译器允许调用兄弟类中的受保护方法,但调用基类
【发布时间】:2014-05-13 10:55:17
【问题描述】:

在我看来,下面的代码应该无法编译,因为方法 TSubB.DoSomething 是受保护的,因此在 TSubA.DoSomething 中是不可见的。 (他们是兄弟姐妹,而不是父/子。)事实上它编译并且当你运行它时,它实际上调用TBase.DoSomething。 (我被这个烫伤了,因为我忘记了DoSomething 受到保护。)

现在变得很奇怪。如果我将 uBase.pas 中的代码粘贴到 Project1.dpr 并从项目中删除 uBase.pas,我确实会遇到编译器错误在那条线上。

谁能解释这是怎么回事?

(抱歉粘贴了这么多代码。这似乎确实是最小的测试用例。)

Project1.dpr

program Project1;
{$APPTYPE CONSOLE}

uses
  uBase in 'uBase.pas',
  uSubB in 'uSubB.pas';

var
  obj : TBase;
begin
  obj := TSubA.Create;
  Writeln(obj.Something);
  obj.Free;
end.

uBase.pas

unit uBase;
interface
type
  TBase = class (TObject)
  protected
    class function DoSomething : string; virtual;
  public
    function  Something : string;
  end;

  TSubA = class (TBase)
  protected
    class function DoSomething : string; override;
  end;

implementation
uses
  uSubB;

function TBase.Something : string;
begin
  Result := DoSomething;
end;

class function TBase.DoSomething : string;
begin
  Result := 'TBase'; // Override in subclass.
end;

class function TSubA.DoSomething : string;
begin
  Result := 'Same as ' + TSubB.DoSomething; // Expect compiler error here
end;
end.

uSubB.pas

unit uSubB;

interface

uses
  uBase;

type
  TSubB = class (TBase)
  protected
    class function DoSomething : string; override;
  end;

implementation

class function TSubB.DoSomething : string;
begin
  Result := 'TSubB';
end;

end.

编辑

如果将所有代码从 uBase.pas 移至 Project1.dpr 并从项目中删除 uBase.pas,则编译器将不再接受对 TSubB.DoSomething 的调用。我不确定为什么这在编译器的可见性方面有任何不同。

修订的 Project1.dpr

program Project1;
{$APPTYPE CONSOLE}

uses
//  uBase in 'uBase.pas',
  uSubB in 'uSubB.pas';

type
  TBase = class (TObject)
  protected
    class function DoSomething : string; virtual;
  public
    function  Something : string;
  end;

  TSubA = class (TBase)
  protected
    class function DoSomething : string; override;
  end;

function TBase.Something : string;
begin
  Result := DoSomething;
end;

class function TBase.DoSomething : string;
begin
  Result := 'TBase'; // Override in subclass.
end;

class function TSubA.DoSomething : string;
begin
  Result := 'Same as ' + TSubB.DoSomething; // Actual compiler error
end;

var
  obj : TBase;
begin
  obj := TSubA.Create;
  Writeln(obj.Something);
  obj.Free;
end.

【问题讨论】:

标签: delphi virtual-functions


【解决方案1】:

TSubB.DoSomething 确实不可见。但是编译器会在所有祖先类中查找DoSomething。它找到的第一个是可见的TBase.DoSomething。因此程序编译。

至于您的编辑,这会改变一切。编辑后,您有两个不同的 TBase 类。一个在 dpr 文件中定义,一个在uBase 单元中定义。那是你的意图。在 dpr 文件中定义基类确实没有意义,因为单元不能使用 dpr 文件。

【讨论】:

  • 当我调用特定的类类型(而不是对象实例)时,为什么它应该在祖先类中查找对我来说并不明显,尽管我可以接受“它们只是规则”。为什么将 TBase 和 TSubA 的定义移动到 Project1.dpr 会阻止编译器找到祖先类,这对我来说更麻烦。也许我需要从我的大脑中消除一些重大的误解......!
  • @IanGoldby 编译器总是搜索它的祖先。例如,考虑如何找到Free 方法。当您在其中一个类上调用 Free 时,编译器在您的派生类中找不到它。它必须一直搜索到TObject 才能找到它。这只是标准方法分辨率。当您将 TBaseTSubA 移动到 .dpr 文件中时,这些类仅在 .dpr 文件中可见。你的其他单位都看不到它们。单位无法看到 .dpr 文件中的声明。
  • 对不起,我在第一点上是封闭的。当然,它会搜索它的祖先;即使 obj 被声明为 TSubA(而不是 TBase),它也会这样做,因此在调用类类型时也必须如此。与多态无关。
  • 关于第二点 - 摆脱 uBase.pas - 我看不出可见性的变化方式。 TSubA.DoSomething 可以看到 TBase.DoSomething 但看不到 TSubB.DoSomething,无论前两者是在 uBase.pas 中还是在 Project1.dpr 中。能见度有何变化?
  • 如果你将方法设为非虚拟方法,那么它会变得非常有趣。您对TSubB.DoSomething 的呼叫可以绑定到TBase.DoSomethingTSubB.DoSomething,具体取决于您拨打电话的单位。这是 Delphi 基于文件的可见性规则的奇怪后果之一。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2013-05-30
  • 2011-04-07
  • 2011-05-18
  • 2013-09-27
  • 1970-01-01
  • 2019-12-06
  • 1970-01-01
相关资源
最近更新 更多