Лекция: Обобщение и типизация

 

Вопросы построения наследственных иерархий тесно связаны с типизацией (в языках с сильной типизацией), поскольку при использовании наследования формируется и система типов.

При определении класса его суперкласс можно объявить public (как в нашем примере). В этом случае открытые и защищенные члены суперкласса становятся открытыми и защищенными членами подкласса. Таким образом, подкласс считается также и подтипом, то есть обязуется выполнять все обязательства суперкласса. В частности, он обеспечивает совместимое с суперклассом подмножество интерфейса и обладает неразличимым с точки зрения клиентов суперкласса поведением.

Если при определении класса объявить его суперкласс как private, это будет означать, что, наследуя структуру и поведение суперкласса, подкласс уже не будет его под­типом. Открытые и защищенные члены суперкласса станут зак­рытыми членами подкласса, и, следовательно, они будут недоступны подклассам более низкого уровня. В этом случае подкласс и суперкласс обладают несовместимыми интер­фейсами с точки зрения клиента.

Если объявить суперкласс protected, то открытые и защищенные элементы такого суперкласса станут защищенными элементами подкласса. Однако, с точки зрения клиента интер­фейсы класса и суперкласса несовместимы.

Пример. Продолжим рассмотрение наследственной иерархии, связанной с фигурами в графической системе. Сделаем следующие объявления:

 

Circle C1;

SolidCircle SC1, SC2;

 

Присвоение объекту А значения объекта В в языке С++ до­пустимо, если тип объекта В совпадает с типом объекта А или является его подтипом.

Поскольку SolidCircle является открытым подклассом Circle, следующий оператор присваивания правомочен:

 

C1 = SC1;

 

Хотя он формально и правилен, но опасен: любые дополнения в состоянии под­класса по сравнению с состоянием суперкласса срезаются. Таким образом, дополнительный атрибут fillcol, определенный в подклассе SolidCircle, будет по­терян при копировании, поскольку его просто некуда записать в объекте класса Circle.

Следующий оператор недопустим:

 

SC2 = С1; // ошибка

 

Изменим описание класса SolidCircle на следующее:

 

class SolidCircle: Circle{.. .};

 

Теперь суперкласс Circle по умолчанию объявлен закрытым. Следовательно, его методы, например, move ( ), недоступны клиентам. Поскольку класс SolidCircle не является теперь подтипом Circle, мы уже не сможем присваивать экземпляры подкласса объектам суперкласса, как в случае объяв­ления суперкласса в качестве открытого.

 

С1 = SC1; // теперь нельзя

 

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

 

class SolidCircle: Circle{

public:

.. .

Circle:: move;

};

 

Правила C++ запрещают делать унаследованный эле­мент в подклассе «более открытым», чем в суперклассе. Например, член, объявленный в суперклассе защищенным, не может быть сделан в под­классе открытым путем явного упоминания.

С наследованием связан особый тип полиморфизма – включение (чистый полиморфизм).Данный тип полиморфизма реализуется при вызове виртуальных функций для указателей (ссылок) на объекты. При открытом наследовании указатель родительского класса может указывать на объекты всех подклассов. Если виртуальная функция имеет различные реализации в подклассах, то выбор, какую ее реализацию вызывать, определяется с учетом выяснения подтипа на этапе выполнения. То есть виртуальная функция вызывается в зависимости не от типа указателя, а от реального типа объекта, на который он указывает. Данная ситуация называется механизмом позднего связывания.

Пример. Опишем наследственную иерархию

 

class One {

public: virtual f ( ){return 1;}

};

class Two: public One {

public: virtual f ( ){return 2;}

};

 

Рассмотрим следующий фрагмент кода:

 

One one, *p;

Two two;

.. .

p = &one; p -> f ( ); // р указывает на объект типа One, f ( ) возвратит 1

p = &two; p -> f ( ); // р указывает на объект типа Two, f ( ) возвратит 2

one.f ( ); // f ( ) возвратит 1

one = two; one.f ( ); // one – объект типа One, f ( ) возвратит 1

 

С дугой стороны, если бы функция f ( ) не была виртуальной, то ее описание в обоих классах было бы не включением, а перегрузкой, и во всех случаях возвращалось бы значение 1.

Для безопасного выявления типа, на который направлен во время работы программы указатель родительского класса или ссылка на него, служит механизм динамического определения типа, специально введенный в язык С++.

Одним из инструментов данного механизма является оператор приведения dynamic_cast. Рассмотрим случай с указателем.

 

dynamic_cast < Тype* > (p);

 

Рассмотрим приведение потомка к типу родителя, которое называется повышающим приведением. Во время выполнения программы определяется реальный тип объекта, на который указывает указатель р. Если р типа Тype* или является указателем на открытый родительский класс для Тype, то результат будет точно такой же, как при простом присваивании р указателю типа Тype*. В противном случае возвращается нуль.

Пример. В предположении, что Circle – закрытый родительский класс класс для SolidCircle, рассмотрим следующий фрагмент кода:

 

Shape *S;

Circle *C;

SolidCircle *SС;

.. .

S = С; // правильно

S = dynamic_cast < Shape* > (С); // эквивалентно предыдущему

S = dynamic_cast < Shape* > (SС); // правильно, возвратится 0

S = SС; // ошибка

 

Приведение родителя к типу потомка называется понижающим приведением. Оно применяется только к классам, имеющим виртуальные функции. Если тип р – Тype* или его подтип, то оператор возвращает указатель на объект, иначе возвращается нуль.

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

 

bool isCircle(Shape* ptr){

Circle* cptr = dynamic_cast < Circle*> (ptr);

return cptr != 0;

}

 

Использование функции иллюстрирует следующий фрагмент:

 

Circle C;

Triangle T;

.. .

isCircle(&C); // true

isCircle(&Т); // false

 

 

еще рефераты
Еще работы по информатике