Страницы

Поиск по вопросам

четверг, 7 марта 2019 г.

Вариативность функций во FreePascal

Первая ситуация: Есть класс, в котором объявлена процедура
procedure SetDefaultValue(DefValue:integer); procedure SetDefaultValue(DefValue:real); procedure SetDefaultValue(DefValue:string); procedure SetDefaultValue(DefValue:char); procedure SetDefaultValue(DefValue:boolean);
Аналогичные конструкции можно видеть и в родных подключаемых модулях Lazarus, например fpjson. При этом, как я понимаю, будет вызвана та подпрограмма, в аргументах которой указана переменная того же типа, что и переданная в вызове процедуры. Почему это работает, несмотря на отсутствие директивы overload? Когда эту директиву нужно использовать, а когда можно (или нужно) опустить?
Вторая ситуация: Процедуры объявлены в родительском классе
procedure SetDefaultValue(DefValue:integer);dynamic;abstract; procedure SetDefaultValue(DefValue:real);dynamic;abstract; procedure SetDefaultValue(DefValue:string);dynamic;abstract; procedure SetDefaultValue(DefValue:char);dynamic;abstract; procedure SetDefaultValue(DefValue:boolean);dynamic;abstract;
И по одной переопределены в пяти классах потомках, с дерективой override. Будет ли работать такая конструкция и, если нет, то почему?


Ответ

Почему это работает, несмотря на отсутствие директивы overload? Когда эту директиву нужно использовать, а когда можно (или нужно) опустить?
Как нам сообщает документация FreePascal (здесь и далее перевод мой, а ссылки отсюда, в квадратных скобках спорные участки перевода):
Директива overload уведомляет компилятор, что эта функция перегружена. Главным образом, в целях обеспечения совместимости с Delphi, все функции и процедуры во FreePascal могут быть перегружены без использования данной директивы.
Однако, это не означает, что директива overload не используется. Другой раздел той же документации сообщает следующее:
До версии компилятора 1.9 все [объявления] перегруженной функции должны были находиться в одном модуле. Сейчас компилятор продолжает поиск соответствия в других модулях, если не нашёл его в текущем, а функция имеет директиву overload
То есть, директиву overload необходимо использовать только для функций, объявленных в разных модулях вашей программы.
Будет ли работать такая конструкция и, если нет, то почему?
Да, будет.
Однако, не следует использовать подобные конструкции, кроме случаев, когда абстрактные методы объявлены в мета-классах, то есть классах, единственное предназначение которых - хранить поля, свойства и методы, общие для всех классов-потомков мета-класса. Проще говоря, мета-класс - это класс, который никогда не заявляется как тип переменной и экземпляры которого никогда не будут создаваться.
var Meta: TMyMetaClass; //Так с мета-классами делать нельзя.
begin Meta := TMyMetaClass.Create; //И так тоже. end.
Нарушение этого правила однажды закончится для вас страшным геморроем и неуловимыми, как ковбой Джо, ошибками, и вот почему (спасибо Grundy за разъяснения):
Предположим, мы объявили родительский мета-класс A
Type A = Class public val: integer; vals: string; public procedure SetDefaultValue(DefValue:integer);virtual;abstract; procedure SetDefaultValue(DefValue:string);virtual;abstract; end;
И два его класса-потомка, B и C
B = Class(A) public procedure SetDefaultValue(DefValue:integer);override; end; C = Class(A) public procedure SetDefaultValue(DefValue:string);override; end;
Описали наши два метода
procedure B.SetDefaultValue(DefValue:integer); begin val := DefValue; end;
procedure C.SetDefaultValue(DefValue:string); begin vals := DefValue; end;
А затем решили: "А зачем нам объявлять переменные двух разных типов, когда есть наследование?" И написали...
var bb, cc: A;
...получив на свою голову кучу проблем. Потому что такой код:
begin bb := B.Create; bb.SetDefaultValue(10); cc := C.Create; cc.SetDefaultValue(10); end.
Великолепно скомпилируется, чтобы, затем, вызвать ошибку выполнения. Что произошло? Сейчас, на маленьком коде-примере, это понять легко, но словив такую ошибку в многомодульном приложении на три тысячи строк вы рискуете искать её часами, без гарантии, что вообще найдёте. Следите за руками: Мы создали экземпляр класса С
cc := C.Create;
В котором, ранее, переопределили один метод
procedure SetDefaultValue(DefValue:string);override;
Принимающий, в качестве аргумента, переменную типа string. А затем вызвали метод
cc.SetDefaultValue(10);
Передав ему аргумент типа integer. "Но это же должно вызвать ошибку компилятора, почему, тогда программа успешно запустилась?", - спросите вы. Неверно, компилятор не видит в вашем коде ошибки, потому что, формально, её там нет. Напомнить, какого типа переменная cc?
cc: A;
А у класса A есть метод:
procedure SetDefaultValue(DefValue:integer);virtual;abstract;
К которому вы и обращаетесь. Но этот метод - абстрактный, он является лишь указанием для программы искать в области видимости экземпляра класса такой же метод с директивой override, которого она не находит, так как он определён только в классе B, но не C, о чём, при компиляции, вас предупредил Lazarus:
Warning: Constructing a class "С" with abstract method "SetDefaultValue"
Что и вызывает ошибку выполнения. В то же время, такой код:
var bb: B; cc: C;
begin bb := B.Create; bb.SetDefaultValue(10); cc := C.Create; cc.SetDefaultValue(10); end.
Просто вызовет ошибку компиляции на предпоследней строке, которую всегда легко найти и устранить.
Примечание для внимательных читателей: директива dynamic семантически эквивалентна virtual. Первая оптимизирована для памяти, вторая - для скорости.

Комментариев нет:

Отправить комментарий