为什么在“重载”方法中将参数类型从“ const”切换为“ var”时无法传递“ Child”类实例

时间:2019-03-22 22:13:16

标签: delphi delphi-10.2-tokyo

MCVE:

在类{{1}的重载方法const中将参数类型从var切换到outTrain时,以下代码不会错误编译}

,但如果指定为non,则会编译。

  

[dcc32错误] Project14.dpr(41):E2250没有重载版本   这些参数可以调用的“火车”

TAnimalTrainer

找到解决方法:

  • 省略program Project14; {$APPTYPE CONSOLE} {$R *.res} uses System.SysUtils; type TAnimal = class private FName: string; end; TDog = class(TAnimal) public constructor Create(Name: string); end; TAnimalTrainer = record // class or record public procedure Train({const}var aA: TAnimal); overload; // class method or not procedure Train(const aName: string); overload; end; { TAnimalTrainer } procedure TAnimalTrainer.Train(const aName: string); var Dog: TDog; begin Dog := nil; try Dog := TDog.Create(aName); Train(Dog); // error here finally Dog.Free; end; end; procedure TAnimalTrainer.Train(var aA: TAnimal); begin aA := nil; end; { TDog } constructor TDog.Create(Name: string); begin FName := Name; end; begin try { TODO -oUser -cConsole Main : Insert code here } except on E: Exception do Writeln(E.ClassName, ': ', E.Message); end; end.
  • 将本地变量转换为var
  • 坚持使用TAnimal(Dog)

问题:这是编译器中的错误吗?

2 个答案:

答案 0 :(得分:8)

  

这是编译器中的错误吗?

不,不是。

尽管您是在重载方法的上下文中发现此问题的,但重载掩盖了实际问题。如果我们消除了过载,将更容易理解该问题。

因此,为此,请考虑以下程序:

type
  TAnimal = class
  end;

  TDog = class(TAnimal)
  end;

procedure GetAnimal(var AAnimal: TAnimal);
begin
  AAnimal := TAnimal.Create;
end;

var
  Dog: TDog;

begin
  GetAnimal(Dog);
end.

由于以下错误,无法在对GetAnimal的调用中进行编译:

[dcc32 Error]: E2033 Types of actual and formal var parameters must be identical

为什么编译器拒绝这个?好吧,想象一下它是否接受了这一点。如果这样做的话,那么当GetAnimal返回Dog变量时,将引用的对象不是TDog

要查看此内容,请将程序主体更改为如下所示:

GetAnimal(TAnimal(Dog));
Writeln(Dog.InheritsFrom(TDog));

这样做时,程序会编译,但是输出是

FALSE

在程序的上下文中,编译器面临一些重载。如本例所示,编译器无法接受将TDog变量传递给TAnimal var参数,因此它拒绝该重载。它知道无法将TDog变量传递给string参数,因此将其拒绝。此时,将不存在任何重载方法,因此会出现错误消息。

答案 1 :(得分:3)

参数var不匹配的基本问题是,您最终在调用变量中输入错误的类型。

您可以通过使用absolute关键字来诱骗编译器执行此操作-允许您声明共享同一空间的不同类型的变量-并模拟如果编译器允许您使用这种构造会发生的情况。

考虑以下示例

uses
  System.SysUtils;

type
  TAnimal = class
  public
    procedure Run; virtual;
  end;

  TDog = class(TAnimal)
  public
    procedure Bark; virtual;
    procedure Fetch; virtual;
  end;

  TCat = class(TAnimal)
  public
    procedure Meow; virtual;
  end;

procedure TAnimal.Run;
begin
  Writeln('Run');
end;

procedure TDog.Bark;
begin
  Writeln('Bark');
end;

procedure TDog.Fetch;
begin
  Writeln('Fetch');
end;

procedure TCat.Meow;
begin
  Writeln('Meow');
end;

procedure Move(const aA: TAnimal);
begin
  aA.Run;
end;

procedure Train(var aA: TAnimal);
begin
  aA := TCat.Create;
end;

var
  Dog: TDog;
  Cat: TAnimal absolute Dog;
begin
  try
    // we cannot use Dog here, because compiler would refuse to compile such code
    // Cat is TAnimal and compiler allows to pass it
    // since Dog and Cat variables share same address space that is
    // equivalent of calling Train(Dog);
    Train(Cat); 

    Move(Cat);
    Dog.Bark;
  except
    on E: Exception do
      Writeln(E.ClassName, ': ', E.Message);
  end;
end.

如果运行上面的代码,您将得到以下输出

Run
Meow

DogCat变量共享相同的地址空间,因此,当您调用Train(Cat)时,您将获得TCat实例,可以通过{{ 1}}或Cat变量。基本上,您最终会在Dog变量内找到TCat实例。

很显然,当您调用TDog时,应该得到Dog.Bark而不是Bark的输出。 MeowMeow中的第一个方法,就像TCatBark中的第一个方法一样,当通过TDog解析Bark地址时虚拟方法表,它将找到TCat方法。由于这两种方法都具有相同的签名,如果您认为输出错误是可以的,那么一切都很好。

现在,如果您尝试调用Meow,则应用程序将因AV崩溃。 Dog.Fetch类中的对应地址处没有匹配的方法,并且您基本上是在内存中调用一些未初始化的位置,而不是适当的方法。

这说明了为什么TCatvar参数类型必须与调用者变量类型匹配。

关于为什么可以将outTDog作为TCat TAnimal或值参数进行传递。 constTDog都继承自TCat,无论您使用TAnimal实例做什么,TAnimalTDog都支持。它们可以覆盖特定的行为,因此您的猫可以与狗不同地奔跑,但是您所做的一切都是明确的。您最终无法运行一些不存在的代码。

TCat

当然,如果procedure Move(const aA: TAnimal); begin aA.Run; aA.Fetch; // this will fail to compile - there is no Fetch method in TAnimal class end; 实际上是Fetch,这不会阻止您测试特定的类并使用类型强制转换来调用TAnimal

TDog

但是,如果滥用类型转换和类型转换而没有检查特定变量是否实际上是procedure Move(const aA: TAnimal); begin aA.Run; if aA is TDog then TDog(aA).Fetch; end; 实例,则会再次进入AV。

TDog