Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
Cpp_Страуструп.doc
Скачиваний:
16
Добавлен:
03.05.2015
Размер:
3.2 Mб
Скачать

1.5.1 Механизм вызова

Основное средство поддержки объектно-ориентированного программирования

- это механизм вызова функции-члена для данного объекта, когда истинный

тип его на стадии трансляции неизвестен. Пусть, например, есть указатель

p. Как происходит вызов p->rotate(45)? Поскольку С++ базируется на

статическом контроле типов, задающее вызов выражение имеет смысл только

при условии, что функция rotate() уже была описана. Далее, из обозначения

p->rotate() мы видим, что p является указателем на объект некоторого

класса, а rotate должна быть членом этого класса. Как и при всяком

статическом контроле типов проверка корректности вызова нужна для того,

чтобы убедиться (насколько это возможно на стадии трансляции), что типы в

программе используются непротиворечивым образом. Тем самым гарантируется,

что программа свободна от многих видов ошибок.

Итак, транслятору должно быть известно описание класса, аналогичное

тем, что приводились в $$1.2.5:

class shape

{

// ...

public:

// ...

virtual void rotate ( int );

// ...

};

а указатель p должен быть описан, например, так:

T * p;

где T - класс shape или производный от него класс. Тогда транслятор

видит, что класс объекта, на который настроен указатель p, действительно

имеет функцию rotate(), а функция имеет параметр типа int. Значит,

p->rotate(45) корректное выражение.

Поскольку shape::rotate() была описана как виртуальная функция, нужно

использовать механизм вызова виртуальной функции. Чтобы узнать, какую

именно из функций rotate следует вызвать, нужно до вызова получить из

объекта некоторую служебную информацию, которая была помещена туда при его

создании. Как только установлено, какую функцию надо вызвать, допустим

circle::rotate, происходит ее вызов с уже упоминавшимся контролем типа.

Обычно в качестве служебной информации используется таблица адресов

функций, а транслятор преобразует имя rotate в индекс этой таблицы. С

учетом этой таблицы объект типа shape можно представить так:

center

vtbl:

color &X::draw

&Y::rotate

...

...

Функции из таблицы виртуальных функций vtbl позволяют правильно

работать с объектом даже в тех случаях, когда в вызывающей функции

неизвестны ни таблица vtbl, ни расположение данных в части объекта,

обозначенной ... . Здесь как X и Y обозначены имена классов, в которые

входят вызываемые функции. Для объекта circle оба имени X и Y есть circle.

Вызов виртуальной функции может быть по сути столь же эффективен, как

вызов обычной функции.

1.5.2 Проверка типа

Необходимость контроля типа при обращениях к виртуальным функциям

может оказаться определенным ограничением для разработчиков библиотек.

Например, хорошо бы предоставить пользователю класс "стек чего-угодно".

Непосредственно в С++ это сделать нельзя. Однако, используя шаблоны типа и

наследование, можно приблизиться к той эффективности и простоте

проектирования и использования библиотек, которые свойственны языкам с

динамическим контролем типов. К таким языкам относится, например, язык

Smalltalk, на котором можно описать "стек чего-угодно". Рассмотрим

определение стека с помощью шаблона типа:

template < class T > class stack

{

T * p;

int sz;

public:

stack ( int );

~stack ();

void push ( T );

T & pop ();

};

Не ослабляя статического контроля типов, можно использовать такой стек

для хранения указателей на объекты типа plane (самолет):

stack < plane * > cs ( 200 );

void f ()

{

cs.push ( new Saab900 ); // Ошибка при трансляции :

// требуется plane*, а передан car*

cs.push ( new Saab37B );

// прекрасно: Saab 37B - на самом

// деле самолет, т.е. типа plane

cs.pop () -> takeoff ();

cs.pop () -> takeoff ();

}

Если статического контроля типов нет, приведенная выше ошибка

обнаружится только при выполнении программы:

// пример динамическое контроля типа

// вместо статического; это не С++

Stack s; // стек может хранить указатели на объекты

// произвольного типа

void f ()

{

s.push ( new Saab900 );

s.push ( new Saab37B );

s.pop () -> takeoff (); // прекрасно: Saab 37B - самолет

cs.pop () -> takeoff (); // динамическая ошибка:

// машина не может взлететь

}

Для способа определения, допустима ли операция над объектом, обычно

требуется больше дополнительных расходов, чем для механизма вызова

виртуальных функций в С++.

Рассчитывая на статический контроль типов и вызов виртуальных функций,

мы приходим к иному стилю программирования, чем надеясь только на

динамический контроль типов. Класс в С++ задает строго определенный

интерфейс для множества объектов этого и любого производного класса, тогда

как в Smalltalk класс задает только минимально необходимое число операций,

и пользователь вправе применять незаданные в классе операции. Иными

словами, класс в С++ содержит точное описание операций, и пользователю

гарантируется, что только эти операции транслятор сочтет допустимыми.