为什么在'overloaded'方法中将参数类型从'const'切换到'var'时,我无法传递'Child'类实例

问题描述 投票:5回答:2

MCVE:

const类的重载方法var中将参数类型从out切换到TrainTAnimalTrainer时,以下代码无法编译时出错

但如果指定了non,则编译。

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

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)
  • 坚持使用const

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

delphi delphi-10.2-tokyo
2个回答
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参数,因此被拒绝。此时,没有剩余的重载方法,因此出现错误消息。


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实例,您可以通过CatDog变量使用。基本上,你最终会在TCat变量中使用TDog实例。

很明显,当你打电话给Dog.Bark时,你应该得到Bark作为输出而不是MeowMeowTCat中的第一种方法,就像BarkTDog中的第一种方法一样,当通过Bark虚方法表解析TCat地址时,它会找到Meow方法。由于两种方法具有相同的签名,如果您认为输出错误,一切都很好。

现在,如果您尝试调用Dog.Fetch,应用程序将与AV崩溃。 TCat类中的相应地址没有匹配的方法,你基本上在内存中调用一些未初始化的位置而不是正确的方法。

这就解释了为什么varout参数类型必须与调用者变量类型匹配。

至于为什么你可以通过TDogTCat作为TAnimal const或值参数。 TDogTCat都继承自TAnimal,你可以用TAnimal实例,TDogTCat都支持它。它们可以覆盖特定的行为,因此您的猫可以与您的狗不同地运行,但无论您做什么,它都是明确定义的。你不能最终运行一些不存在的代码。

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;

但是,如果您滥用类型转换和类型转换而不检查特定变量是否实际上是TDog实例,您将再次进入AV。

procedure Move(const aA: TAnimal);
begin
  aA.Run;
  TDog(aA).Fetch;
end;
© www.soinside.com 2019 - 2024. All rights reserved.