Набрали: Валентин Буров, Илья Тюрин
Вид материала | Лекция |
СодержаниеСнятие механизма виртуального вызова Абстрактные методы. Абстрактные классы. |
- Идз №5 Индивидуальные задания из задачника Тюрин Ю. И., Ларионов В. В., Чернов, 268.29kb.
- Тюрин Сергей Борисович учебно-методический комплекс, 387.27kb.
- Тюрин Сергей Борисович учебно-методический комплекс, 459.22kb.
- Тюрин Сергей Борисович учебно-методический комплекс, 369.3kb.
- Федеральное агентство по образованию (Рособразование) Архангельский государственный, 359.58kb.
- В. Ю. Буров, Н. А. Кручинина малое предпринимательство в забайкальском крае (современное, 2671.76kb.
- Зарипов Рашид Рафкатович Проверил: Нижний Тагил 2004 г задача, 96.68kb.
- Русская литература. Электронный учебник, 348kb.
- А. М. Тюрин Аннотация: Изменения тенденций эволюции языка новгородских берестяных грамот,, 370.04kb.
- Петрик Валентин Михайлович, 487.68kb.
Снятие механизма виртуального вызова
Как мы уже отметили вызовы виртуального и невиртуального методов существенно различаются. Невиртуальный метод вызывается одним косвенным вызовом, тогда как виртуальный вызывается несколько сложнее через несколько указателей. Но вызовы виртуальных методов можно снимать, разумеется, только в методах. Пусть у нас есть:
class X {
virtual void f( );
...
};
class Y public X {
void h( ) { f ( ); ...} // нельзя сказать, из какого класса
//будет вызван метод
// f( ), он может быть вызван из X, Y или какого-то
// наследного класса Z в зависимости от динамического типа
...
};
Если мы знаем, какой метод какого класса хотим получить, то можно снять вызов виртуального метода, написав вместо f ( ), например, X::f(), таким образом мы дадим точно понять, какую реально функцию хотим, и компилятор подставит сюда вызов f( ), как невиртуальной функции. То есть виртуальность и невиртуальность – это механизм вызова, а не самой функции, ибо сама функция реализуется одинаково в обоих случаях.
Заметим, что снять механизм виртуального вызова извне класса невозможно.
^
Абстрактные методы. Абстрактные классы.
Когда мы начинали обсуждать пример связанный с графическими примитивами:
class Shape {
int x,y;
virtual void Draw(bool);
void Move(...);
...
};
Класс Shape – вершина иерархии. Заметим, что в методе Move у нас вызывается Draw. Это напрямую означает, что мы должны написать реализацию Draw, иначе компилятор выдаст ошибку. Но смысла в этой реализации нет никакого, так как она сведется в результате к вызову пустого метода, да и назначение Shape не предусматривает смысла в реализации Draw.
Классы аналоги Shape, из которых потом выводятся другие классы, призваны для определения интерфейсов виртуальных методов.
Из этих соображений были введены абстрактные классы и абстрактные виртуальные методы (не путать с абстрактными типами данных).
class X {
virtural void f( )=0;
};
“=0” и означает то, что f( ) – абстрактный виртуальный метод. Страуструп дико боялся введения новых ключевых слов по вполне понятным причинам, хотя объявление могло бы выглядеть и, например, так:
abstract virtual void f( );
Что такое абстрактный виртуальный метод? Это метод, который указывает на то, что в любом классе, унаследованном от данного функция f( ) должна быть переопределена. Но она не должна быть определена здесь. Классы, в которых есть абстрактные методы называют абстрактными классами. Они отличаются от обычных тем, что объекты данных классов нельзя заводить в программе. Если в классе X есть хотя бы один абстрактный метод, то при попытке создать экземпляр данного класса компилятор выдаст ошибку.
Очевидно, что метод Draw() должен быть абстрактным.
Если мы не переопределим в классе-наследнике абстрактный метод, то порожденный таким образом класс будет также абстрактным. Заметим, что в стандартных библиотеках очень часто можно встретить целые слои абстрактных классов. Это допустимо.
Можно заводить указатели на абстрактные классы и им присваивать адреса производных, уже неабстрактных.
Возникает вопрос, связаны ли между собой абстрактные классы (АК) и абстрактные типы данных (АТД)? На первый взгляд нет. Но представим себе такую вещь. Пусть есть класс, в котором данные отсутствуют вообще, а все методы объявлены, как виртуальные и абстрактные:
class Abstract {
public:
virtual void f()=0;
virtual void g()=0;
};
Раньше имело смысл заводить класс без данных в случае, если все функции в нем статические. Здесь речь идет о функциях-членах и в классе нет никаких данных. Утверждается, что это в самом чистом виде абстрактный тип данных. Реализация данного типа данных – выведение неабстрактного типа данных. Приведем стандартный пример, пример множества:
class Set {
public:
virtual void Include (Set & S)=0;
virtual void Include (Elem & S)=0;
virtual void Exclude (Elem &S)=0;
...
};
Конечно, хочется еще сделать этот класс и шаблоном, параметризовав его типом Elem. Вобщем, мы получили АК без данных, что очень похоже на АТД. Ведь АТД это тип, где данные и код полностью инкапсулированы и интерфейс представлен лишь набором операций. Что является реализацией? В объектно-ориентированных языка в отличие от остальных появляется еще одна степень свободы:
class Slist {...}; // операции работы с линейным списком
class ListSet: public Set, public Slist {
...// благодаря множественному наследованию переопределяются
//мы наследуем и интерфейс и реализацию.
// следует, конечно, переписать все операции Set через операции
// SList
};
Теперь всем клиентским модулям, использующим Set, можно подставлять класс ListSet, и они будут прекрасно работать. Но клиентские функции ничего не знают реализации. Отсюда следует еще и минимизация перекомпиляции – ведь реализацию класса ListSet мы можем поместить куда угодно.
Информация о классе Set представляет из себя указатель на ТВМ.
Здесь мы получаем наиболее гибкое отделение реализации от определения.
Интересно, что объекты ListSet будут представлять из себя одну ссылку на ТВМ и набор данных:
ссылка на ТВМ |
DATA |
причем не произойдет никаких «размножений» ТВМ, так как один из классов-предков имел ТВМ, а другой только данные. Мы даже можем написать универсальный контейнер, который с помощью линейного списка реализует множество, стек, очередь Q – где все они будут являться АК.
Поскольку данные наследуются только по одному пути (или вообще не наследуются, а указываются в наследнике), то мы лишаемся всех тех проблем, которые были связаны с наследованием данных из разных источников, они решены в C++, но как-то неестественно.