Объектно-ориентированное программирование, страница 13

Где можно пользоваться унаследованным методом Class()? Если убрать метод Out из описания класса Stud, то можно ли воспользоваться унаследованной версией Out для вывода имени объекта stud? Где этот метод доступен для объектов класса Stud?

Что произойдет, если вместо заголовка class Prof : public Man задать другой заголовок класса class Prof : protected Man? Можно ли изменить поле name внутри какого-либо метода класса Prof? Как изменить это поле в методе класса Prof? Если в классе Parent предыдущего примера определен public-метод void pr(), то как вызвать этот метод для поля par объекта d класса Derived? То же, но с помощью указателя на класс Derived?

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

В предыдущем разделе была выстроена иерархия из трех классов, в которой каждый новый класс является производным от одного ранее декларированного. Язык С++ позволяет выстраивать и более сложные, древовидные и даже перекрещивающиеся иерархические структуры из классов. Упомянем, например, дерево классов, порождаемое классом ios из библиотеки iostream. Подобные связи удается организовать благодаря механизму множественного наследования. Приведем другой пример: класс Transistor может быть общим ребенком классов Diode и Thyristor. В литературе, наряду с термином Derived (производный) Class, используется термин Child Class. Аналогично, синонимами являются термины: Parent Class и Base Class. При множественном наследовании атрибуты доступа (прав пользования) указываются отдельно для каждого родительского класса. Например,

class A : private B, protected C, D, public E {...};

Все данные и методы класса B наследуются классом A с правами private, члены класса C наследуются им же с правами protected, члены класса D наследуются с правами private (по умолчанию), а члены класса E — с правами public. Таким образом, protected- и public-члены класса C попадают в секцию protected класса A. Все члены классов B и D попадают в секцию private класса A. Все члены класса E наследуются с сохранением исходных прав доступа. Наследованные классом A private-члены всех классов, как было показано выше, доступны только с помощью методов, унаследованных классом A от своих классов. Множественное наследование позволяет строить сложные иерархические структуры, в которых может случиться так, что некоторый класс объявлен предком другого класса более, чем один раз. Например,

class A {...};

class B : public A {...};

class C : public A {...};

class D : public A, public B, public C, {...};

Здесь каждый объект класса D содержит наследованные члены класса A в трех экземплярах. Если это не нужно и мешает работе с объектом, то следует добавить описатель virtual при указании родительского класса.

class A {...};

class B : public virtual A {...};

class C : public virtual A {...};

class D : public A, public B, public C, {...};

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

Полиморфизм и виртуальные функции

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

Главным моментом в понимании сути полиморфизма является момент времени, в который система определяет детали реализации метода для объекта. Если система решает как выполнить действие во время компиляции, то этот процесс называется ранним связыванием (early binding), если решение принимается динамически во время исполнения программы, то это называется поздним связыванием (late binding). Под связыванием понимают связывание сути послания (имени метода) с конкретными кодами его реализации для данного объекта.

Раннее связывание дает преимущество в скорости, так как компилятор имеет возможность оптимизировать код до его выполнения. Позднее связывание дает преимущество в гибкости и высоте абстракции задачи. Если метод или операция переопределены в двух или более классах, то компилятор может связать послание объекту с конкретными кодами заранее, так как он знает класс объекта, которому послано данное сообщение. Рассмотренное нами ранее переопределение методов является по сути простейшей формой полиморфизма. Ниже рассматривается полиморфизм позднего связывания, в котором конкретные коды метода выбираются на этапе исполнения программы.

Виртуальные функции являются средством, с помощью которого полиморфизм позднего связывания осуществляется на практике. Если в базовом классе Parent определить какую-нибудь функцию (метод) как virtual, то она может быть переопределена (overriden) в одном из (или более) производных классов. Преимущества такого определения функций в родственных классах будут ясны после того, как мы вспомним, что дает переопределение обычной, не виртуальной функции в производном классе Derived. Пусть для определенности в обоих классах была объявлена public-функция void print(). Рассмотрим теперь фрагмент функции main, использующий объекты классов Parent и Derived.

  Parent  *pp, pobj;

Derived *pd, dobj;

pobj.print();   // Будут вызваны разные print()

dobj.print();   // Полиморфизм раннего связывания

Компилятор заранее решает, каким классам должны принадлежать функции print в последних двух операторах. Очевидно, что для объекта pobj класса Parent будет вызван метод своего же класса, то есть Parent::print(). Аналогично, для производного класса справедлив вызов Derived::print(). Теперь рассмотрим такие, вполне легальные присвоения:

pp = &dobj;   // Совместимость указателей

pd = pp;