Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
КРАТКИЙ ОБЗОР С.doc
Скачиваний:
1
Добавлен:
26.10.2018
Размер:
2.11 Mб
Скачать

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 класс задает только минимально необходимое число операций, и пользователь вправе применять незаданные в классе операции. Иными словами, класс в С++ содержит точное описание операций, и пользователю гарантируется, что только эти операции транслятор сочтет допустимыми.

1.5.3 Множественное наследование

      Если класс A является базовым классом для B, то B наследует атрибуты A. т.е. B содержит A плюс еще что-то. С учетом этого становится очевидно, что хорошо, когда класс B может наследовать из двух базовых классов A1 и A2. Это называется множественным наследованием.       Приведем некий типичный пример множественного наследования. Пусть есть два библиотечных класса displayed и task. Первый представляет задачи, информация о которых может выдаваться на экран с помощью некоторого монитора, а второй - задачи, выполняемые под управлением некоторого диспетчера. Программист может создавать собственные классы, например, такие:       class my_displayed_task: public displayed, public task       {       // текст пользователя       };       class my_task: public task {       // эта задача не изображается       // на экране, т.к. не содержит класс displayed       // текст пользователя       };       class my_displayed: public displayed       {       // а это не задача       // т.к. не содержит класс task       // текст пользователя       };       Если наследоваться может только один класс, то пользователю доступны только два из трех приведенных классов. В результате либо получается дублирование частей программы, либо теряется гибкость, а, как правило, происходит и то, и другое. Приведенный пример проходит в С++ безо всяких дополнительных расходов времени и памяти по сравнению с программами, в которых наследуется не более одного класса. Статический контроль типов от этого тоже не страдает.       Все неоднозначности выявляются на стадии трансляции:       class task       {       public:       void trace ();       // ...       };       class displayed       {       public:       void trace ();       // ...       };       class my_displayed_task:public displayed, public task       {       // в этом классе trace () не определяется       };       void g ( my_displayed_task * p )       {       p -> trace (); // ошибка: неоднозначность       }       В этом примере видны отличия С++ от объектно-ориентированных диалектов языка Лисп, в которых есть множественное наследование. В этих диалектах неоднозначность разрешается так: или считается существенным порядок описания, или считаются идентичными объекты с одним и тем же именем в разных базовых классах, или используются комбинированные способы, когда совпадение объектов доля базовых классов сочетается с более сложным способом для производных классов. В С++ неоднозначность, как правило, разрешается введением еще одной функции:       class my_displayed_task:public displayed, public task       {       // ...       public:       void trace ()       {       // текст пользователя       displayed::trace (); // вызов trace () из displayed       task::trace (); // вызов trace () из task       }       // ...       };       void g ( my_displayed_task * p )       {       p -> trace (); // теперь нормально       }