Основная проблема с несоответствующими параметрами 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
Dog
и Cat
переменные разделяют одно и то же адресное пространствопоэтому, когда вы в результате вызовете Train(Cat)
, вы получите экземпляр TCat
, который вы можете использовать либо через переменную Cat
, либо Dog
.По сути, вы получите TCat
экземпляр внутри переменной TDog
.
Очевидно, что когда вы вызываете Dog.Bark
, вы должны получить Bark
в качестве вывода, а не Meow
.Meow
- первый метод в TCat
, точно так же, как Bark
- первый метод в TDog
, и при разрешении адреса Bark
через таблицу виртуальных методов TCat
он найдет метод Meow
.Поскольку оба метода имеют одинаковую сигнатуру, все в порядке, если вы считаете неправильный вывод правильным.
Теперь, если вы попытаетесь вызвать Dog.Fetch
, приложение завершит работу с AV.В соответствующем адресе класса TCat
нет подходящих методов, и вы в основном вызываете неинициализированное место в памяти вместо правильного метода.
Это объясняет, почему типы параметров var
или out
должны соответствовать вызывающей стороне.тип переменной.
Почему вы можете передать TDog
или TCat
как TAnimal
const
или параметр-значение.И TDog
, и TCat
наследуются от TAnimal
, и все, что вы можете сделать с TAnimal
экземпляром, оба TDog
и 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;
Однако, если вы злоупотребляете приведением и приведением типов без проверки, является ли конкретная переменная на самом деле TDog
экземпляром, вы снова подключитесь к AV.
procedure Move(const aA: TAnimal);
begin
aA.Run;
TDog(aA).Fetch;
end;