Лекция 13 (1160846), страница 2
Текст из файла (страница 2)
2) ищется ближайший заместитель.
Если ни одного заместителя не встретилось, вызывается метод базового класса.
При этом составляется таблица виртуальных методов – содержит виртуальные методы и их адреса.
Как мы работаем с таблицей?
-
вытаскиваем ее адрес
-
находим адрес нужной функции
-
запускаем ее
Все это занимает приблизительно 6 ассемблерных команд – большие накладные расходы по сравнению с обычной командой. Вызов виртуальной функции гораздо более трудоемок, чем вызов невиртуальной функции.
Однако этими накладными расходами в большинстве случаев(если мы, конечно, не пишем собственный компилятор) можно пренебречь(не значит, однако, что нужно!).
Пример:
class X{ public:
virtual void f();
virtual void g();
virtual void h();
};
class Y:X{
void f();
virtual void g()=1;
virtual void h();
};
Как снять виртуальность? Можно просто ообратиться к методу как к статическому, и тогда вся виртуальность снимется.
Пример:
void g(){
X::f();
}
Пример:
рx->X::f(); //тут осуществляется именно доступ по конкретному известному адресу(привязка метода статическая).
Уточним, что такое замещение.
В Java понятия виртального метода вовсе нет – там все методы о определению связаны динамически. Из соображений эффективности компилятор может снять динамичесий вызов, подставив конкретный адрес, если он точно знает тип ссылки.
Пример:
Z z = new Z();
z.f(); //=> z.Z::f();
Как обстоит дело в остальных языках?
С++
Если на верхнем уровне метод определен как виртуальный, то все его замещения будут по определению виртуальными вне зависимости от наличия слова virtual перед определением функции.
А если функция была невиртуальной, а потом вдруг стала виртуальной:
void g();
………….
virtual void g();; //допустимо
X: void g();
Y: virtual void g();
Замечание.
Вместо невиртуальных функций можно всегда подставлть их тело в месте вызова с учетом переименования переменной(с виртуальными функциями так делать нельзя)
С#, Delphi
Виртуальность метода обрывается, если мы не указываем virtual в очередном переопределении.
сlass X {
public virtual void f() {}
};
class Y {
public void f() {} // ошибка.
};
Можно либо так:
сlass X {
public virtual void f() {}
};
class Y {
public virtual void f() {} // продолжаем цепочку
};
Либо так:
сlass X {
public virtual void f() {}
};
class Y {
public override void f() {} // заменить!
};
Если написать так:
сlass X {
public virtual void f() {}
};
class Y {
public override virtual void f() {} // то цепочка заместителей поменяется, но метод останется виртуальным
};
Также заметим, что при повторном написании virtual компилятор выдаст предупреждение(если нет замещения, а есть скрытие), чтобы избавиться от него – надо написать new.
Delphi
type X = class
……………….
procedure f() virtual;
………….
end;
type Y = class(X)
Возможные ситуации
procedure f();// ошибка
procedure f(); override;
procedure f(); overload
ADA 1995
Если есть параметр tегированного типа, для вызова есть специальные типы:
CWT – class wide types – классовый тип
Классовый тип – потенциально бесокнечное множество объектов, включающее все объекты класса Т и все производные объекта.
type T is tagged record …… end
T – класс, Т’ – классовый тип
type A is array (index range <>) of T;
X:A; //-ошибка!
X: A (range L..R); //нормально
X:T’
Тprocedure P (X: T);
T1procedure P (X: T1);
Представим, что существует некоторая глобальная процедура
procedure CALL(A : T); // P(A); -- P(X:T)
procedure CALLV (A : T’class) //P(A);
Тогда:
X: T;
Y:T1;
CALL(X); // - P(T)
CALL(Y); // - P(T)
CALLV(X); // - P(T)
CALLV(Y); // - P(T1)
Если речь идет о вызове классовго типа, то он может быть только динамический.
Замечание1. Вообще говоря, Ада наиболее близко подошла к концепции МУЛЬТИМЕТОДА(метода, связанного по нескольким параметрам). Тем не менее, из соображений эффективности он в ней не реализован.
Мультиметод – это метод, который вызывается в зависимости от динамического типа двух своих ссылок.
CALL_W(X:T’ class, Y: W’ class);
P(X, Y);
Пример использования мультиметодов можно легко привести из компьютерной графики: пересечение объектов – вызываем ту или иную функцию в зависимости от способов пресечения.
Мультиметоды есть, например, в языке CLOS(Common Lisp With Object Systems) – достаточно известный язык в довольно узких кругах.
Замечание 2. Оберон -2 отличается от Оберон тем, что в нем есть процедуры и динамическое приведение к типу – по определению аналог виртуальных методов.
Пример
TYPE T RECORD
……………………..
END;
TYPE T1 RECORD(T)
……………………..
END;
PROCEDURE(VAR X: T) P; //виртуаьная функция, Х передается как ссылка
Перекрытия нет, а замещение есть:
PROCEDURE(VAR X: T1) P; //динамическая(виртуальная) функция
VAR X: T;
Y: T1;
X.P; // -------------P(T)
Y.P; // --------------P(T1)
Вызывать таким образом можно лишь процедры, динамически привязанные к типу.
PROCEDURE CALL (VAR A: T); //обычная функция
Если имя функции написано после скобочек – функция виртуальная, если перед ним – то самая обычная.
//Здорово, да?
Пример:
TYPE PT = POINTER TO T;
X: PT;
X = NEW(T);
X.P ------------------ вызовется PROCEDURE(VAR X: T) P; (процедура, динамически привязанная к типу Т)
X = NEW(T1);
X.P ------------------ вызовется PROCEDURE(VAR X: T1) P; (процедура, динамически привязанная к типу Т1)
Замечание 3
Запрет наследования – очень важный элемент в современных языках.
В C# наследование очень чато запрещается. Пример – закрытый класс Path.
В C# и Java предусмотрены специальные средства для запрета наследования: ключевые слова seаled и final.
Java
final - ставится перед классом, если класс является конечным в иерархии классов, то есть его нельзя наследовать
C#
sealed – «запечатанный» класс
Любой сатический класс в C#3.0 запечатан.
Слова final и sealed могут стоять и перед методами, означая запрет данного метода в наследних в Java и в C#(там sealed имеет смысл ставить только около виртуальных методов).
Sealed может стоять или перед определением, или перед замещением виртуального метода.