Набрали: Валентин Буров, Илья Тюрин
Вид материала | Лекция |
- Идз №5 Индивидуальные задания из задачника Тюрин Ю. И., Ларионов В. В., Чернов, 268.29kb.
- Тюрин Сергей Борисович учебно-методический комплекс, 387.27kb.
- Тюрин Сергей Борисович учебно-методический комплекс, 459.22kb.
- Тюрин Сергей Борисович учебно-методический комплекс, 369.3kb.
- Федеральное агентство по образованию (Рособразование) Архангельский государственный, 359.58kb.
- В. Ю. Буров, Н. А. Кручинина малое предпринимательство в забайкальском крае (современное, 2671.76kb.
- Зарипов Рашид Рафкатович Проверил: Нижний Тагил 2004 г задача, 96.68kb.
- Русская литература. Электронный учебник, 348kb.
- А. М. Тюрин Аннотация: Изменения тенденций эволюции языка новгородских берестяных грамот,, 370.04kb.
- Петрик Валентин Михайлович, 487.68kb.
C++
В C++ виртуальные методы реализованы по такой схеме, и эта схема проста. В частности, виртуальный метод вызывается за, примерно, 7 ассембелрных команд (невиртуальный – за одну).
Чем еще неявно пользуется компилятор? Тем, что ссылка на ТВМ всегда занимает одно и то же место.
Какие накладные расходы есть на реализацию виртуальных методов?
- По памяти. На первый взгляд они небольшие – по одной ТВМ на каждый тип, с другой стороны, мы в объект каждого класса теперь должны добавлять указатель (если в классе есть виртуальные методы).
- По времени. На выполнение тех 7 команд, о которых было упомянуто выше.
Е

ще приятно то, что в случае единичного наследования компилятор знает, какой указатель передавать. Но ситуация меняется кардинально в случае множественного наследования. Страуструпу удалось реализовать множественное наследование достаточно эффективно, хотя это было непросто. Пусть у нас есть два класса: X, Y и наследуемый от них Z.
Тут возникает некоторая неприятность. Как выглядит Z, если забыть о виртуальных функциях? Часть от X, часть от Y, часть своя.
Заметим, что функция g( ) в Z не переопределяется. Пусть у нас есть такая ситуация:
X *px, Y *py, Z *pz;
px=new X; py=new Y; pz=new Z;
теперь сделаем следующее:
px=pz;
px->f( );//будет вызвана Z::f( ), поскольку она переопределяется в Z.
px->g( );//компилятор скажет «имя неопределено», так как функция g( )
// присутствует и в X и в Y, поэтому нам следует явным образом
// указывать, какая функция вызывается – X::g( ), например. Это
// снимает принцип виртуальности.
Теперь представим себе такую ситуацию:
т

огда в ситуации:
px=pz;
px->f( ) // X::f( )
px->g( ) // ошибка, так как в X функция g даже не определена.
Ведь в случае когда вызывается функция от класса Y, ей должен быть передан указатель класса py:
X | px, pz |
Y | py |
Z | |
а у нас реально указатель другой – на начало.
В случае же
pz->g( )
какая функция будет вызвана? Очевидно, Y::g( ), в качестве указателя this ей будет передан pz (на начало вышеприведенной таблицы). Что же получается? А именно то, что в случае множественного наследования в ТВМ должно быть еще смещение. Фактически, мы должны иметь два варианта ТВМ. Если вызываются виртуальные методы от X (в нашем случае это f( ) ), то смещение равно нулю, если же вызываются методы для второго класса (а также третьего, четвертого и т.д.), то нужно добавлять некий сдвиг внутри таблицы, он равен –(размер X).
Z | X | ссылка на ТВМ XZ |
X | ||
Y | ссылка на ТВМ YZ | |
Y | ||
| Z |
TВМ XZ:
Z::f |
Z::i |
ТВМ YZ:
Y::g |
Z::f |
Z::i |
Заметим, что ТВМ Z получается «спрятанным» в совместных ТВМ с другими классами.
Таким образом, для кажого класса, от которого происходит наследование, мы должны иметь совместную ТВМ этого класса и Z (потомка). Разумеется, это не есть хорошо.
Smalltalk
В SmallTalk не линейное, а цепное представление класса:

Начинается все со ссылки на объект базового класса. Плюс каждый объект имеет ссылку на таблицу методов (ТМ), можно не писать слово «виртуальных», так как в SmallTalk все методы виртуальны. Более того, SmallTalk настолько динамический язык, что там можно динамически изменять ТМ – среди методов класса (аналог статических методов в C++) есть методы, которые позволяют добавлять или убирать методы из ТМ данного класса. Получается, что можно динамически менять поведение объекта. В C++ это невозможно.
В SmallTalk в принципе нельзя во время компиляции определить – применим ли данный метод для данного объекта, поэтому вызов метода осуществляется следующим образом: пусть у нас вызывается некий метод MSG, он ищется в ближайшей к нему ТМ, если он там есть, то вызывается, если нет – происходит поиск в следующей ТМ по иерархии вверх (если метод не будет найден, возникнет сообщение об ошибке). Следует отметить, что вызов MSG может оказаться некорректным, если есть несоответствие в параметрах (ведь ТМ может изменяться динамически, и компилятор это несоответсвие отловить просто не может), в этом случае возникнет сообщение об ошибке, но уже во время выполнения.

Понятно, почему в SmallTalk нет множественного наследования. И так накладные расходы велики (хотя бы динамический поиск – это не подставление смещения в C++). Если же мы добавим множественное наследование, то поиск метода будет осуществляться не по линейному списку, а по дереву, что на порядок сложнее и дольше:
Таким образом множественное наследование с точки зрения данных придается критике. Из-за того, что у нас есть некоторые данные, приходится объединять соответствующие ТВМ - если есть два базовых класса, то фактически мы имеем две ТВМ, это неприятно.
Если бы никаких данных не было, то проблема с модификацией указателя отпадала бы и можно было бы реализовать соответствующую вещь эффективнее.