#pascal #freepascal #lazarus
Первая ситуация: Есть класс, в котором объявлена процедура 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. Будет ли работать такая конструкция и, если нет, то почему?
Ответы
Ответ 1
Почему это работает, несмотря на отсутствие директивы 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. Первая оптимизирована для памяти, вторая - для скорости.Ответ 2
Ваши пять функций SetDefaultValue объявлены абстрактными в базовом классе. Значит, работать код будет с наследниками, которые эти функции переписывают (override). Если переменная, указывающая на наследника, объявлена своим собственным типом (типом наследника) и ее собственная версия SetDefaultValue непосредственно видна компилятору, в виртуальности нет ни необходимости, ни смысла. Компилятор вставит ее вызов напрямую. И если для данного типа наследника нужен только вариант с целым, то об остальных ему и знать не надо, и в базовом классе они не нужны. Если переменная, указывающая на наследника, объявлена типом базового класса, как Вы собираетесь определять, какой из пяти вариантов SetDefaultValue можно, а какой - нельзя вызывать?
Комментариев нет:
Отправить комментарий