Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:

Штерн В. - Основы C++. Методы программной инженерии - 2003

.pdf
Скачиваний:
274
Добавлен:
13.08.2013
Размер:
28.32 Mб
Скачать

700Часть IV ^ Расширенное использование С+Ф

ане уточнение отдельного базового класса. Каждый родительский класс вносит свои элементы в производный класс. Производный класс является объединением базовых возможностей.

Примерами использования множественного наследования являются графиче­ ские объекты, счета NOW и классы lost ream в стандартной библиотеке C+ + .

Для графического пакета классы Shape и Position использовались как базовые классы для создания класса Object. Объекты класса Object объединили свойства объектов Shape и Position. Это пример неразумного применения множественного наследования. Графические объекты являются фигурами, но трудно утверждать, что они представляют собой положения. Скорее можно говорить о том, что графи­ ческий объект располагается в каком-то месте.

Для счетов NOW классы представляют собой сберегательные и текущие счета. Это лучший пример использования множественного наследования. Счет NOW действительно объединяет свойства сберегательных и текущих счетов. По нему выплачиваются проценты и разрешается выписывать чеки. Однако если рас­ спросить служащего банка, можно узнать, что бывают исключительные ситуации, когда счет NOW отличается как от сберегательного, так и от текущего счета. Это означает, что преимущества легкого слияния основных характеристик компенси­ руются недостатками подавления свойств, которые не соответствуют друг другу.

Для библиотеки C + + класса iost ream имеет смысл использовать множествен­ ное наследование для слияния характеристик классов входных и выходных пото­ ков. Полученные в результате классы iost ream поддерживают как операции ввода, так и операции вывода, а в производных классах ничего не требуется подавлять.

Обратите внимание на то, что C + + не устанавливает ограничения на количе­ ство базовых классов, которые могут участвовать в формировании производного класса. Все приведенные примеры включают только два базовых класса. Сложно придумать примеры множественного наследования с тремя или четырьмя базовы­ ми классами так, чтобы они имели смысл и не запутывали пользователя. Почему два лучше, чем три или четыре? Кажется, что примеры множественного наследо­ вания с двумя базовыми классами так же трудно понять.

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

Множественное наследование: правила доступа

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

Правила доступа для множественного наследования те же, что и для простого наследования. Доступ к методам класса Derived могут осуществлять общедоступ­ ные и защищенные члены всех других базовых классов без каких-либо ограниче­ ний. Вы не имеете доступ к закрытым членам базовых классов.

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

Способы порождения для множественного наследования те же, что и для про­ стого наследования. При открытом поро>вдении каждый закрытый, защищенный и общедоступный член базового класса имеет те же права доступа в объектах производного класса, что и в базовом объекте. Это наиболее естественный ре­ жим наследования.

Глава 15 • Виртуальные функции и использование наследования

701

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

При закрытом наследовании все базовые члены становятся закрытыми в про­ изводном классе. Подобно простому наследованию, способ порождения по умол­ чанию является закрытым. Режим порождения должен определяться для каждого базового класса отдельно.

 

Рассмотрим два одинаковых базовых класса Base В1 и В2.

class 81

 

 

 

 

{

public:

 

 

 

 

 

 

void

f1();

/ /

открытый сервис

f1()

 

. ..

} ;

 

/ /

оставшаяся часть

класса В1

class В2

 

 

 

 

{

public:

 

 

 

 

 

 

void

f2();

/ /

открытый сервис

f2()

 

. .

. }

;

/ /

оставшаяся часть

класса В2

Объединим их характеристики в порожденном классе Derived и добавим еще одну функцию-член в порожденном классе.

class Derived : public В1, В2

/ /

два базовых

класса

{ public:

/ /

f1(),

f2()

наследуются

void f3();

/ /

f3()

добавляется

к сервисам

... };

/ /

оставшаяся

часть

класса Derived

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

Derived d; d . f l O

d.f2() d.f3()

/ /

присваивание значения объекту Derived

/ /

унаследован из В1

 

/ /

синтаксическая ошибка: f2()

закрытый

/ /

сервисы добавляются в класс

Derived

Это еще одно проявление различий между зарезервированным словом public, используемым для описания прав доступа и для описания способа порождения, В правах доступа область видимости зарезервированного слова public включает столько членов класса, сколько требуется, до тех пор, пока не будет обнаружено другое зарезервированное слово для определения прав доступа. В способе порож­ дения область видимости зарезервированного слова public включает только один идентификатор.

В приведенном выше примере существует только один класс В1, из которого класс Derived наследуется открыто. Для класса В2 используется способ поро>едения по умолчанию (закрытый). Метод f2() становится закрытым в классе Derived и будет не доступен клиентской программе.

702

Часть W ^ 9<лсиь1фенно^ ^к>"сд^: "\-г^\^ ^ле С-^^

Преобразования классов

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

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

В1 Ы;

В2 Ь2; Derived d;

 

В1 = d;

Ь2 = d;

//OK; дополнительные возможности отвергаются

d = Ы;

d = Ь2;

/ / ошибка: несогласованное состояние объекта

Преобразование из базового класса в производный класс не допускается. Ба­ зовый объект содержит только часть данных и возможностей, которыми распола­ гает производный объект, а пропущенные возможности не могут быть добавлены. Такое преобразование не безопасно.

Подобные же правила применяются к указателям и ссылкам. Указатель (ссыл­ ка) базового класса может безопасно указывать на объект производного класса. Объект производного класса может выполнять все то же, что и базовый указа­ тель. Это безопасно. Однако базовый указатель может вызвать любую часть возможностей производного объекта.

81 *р1; В2 *р2;

Derived *d;

 

 

 

Р1 = new Derived; р2 = new Derived;

/ /

OK: безопасно

 

d = new B1; d = new B2;

/ /

синтаксические

ошибки

d = p1; d = p2;

 

/ /

синтаксические

ошибки

d = (Derived*)

p1;

/ /

OK: явное приведение

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

Подобным образом базовый указатель (который, по-видимому, указывает на базовый объект) не может быть скопирован в указатель производного класса (четвертая строка примера). Это не безопасно. Производный указатель может по­ требовать сервисы, которые базовый объект не в состоянии выполнить, а компи­ лятор не может за этим проследить. Следовательно, манипулирование указателем также рассматривается как синтаксическая ошибка.

Как поступить, если известно, что базовый указатель ссылается на объект производного класса, а не на базовый объект? Укажите компилятору, что вам известно, что вы делаете с помощью приведения типов.

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

void fool (В1 *b1)

/ /

производные объекты содержат

{

b1->fl();

}

/ /

дополнительные свойства

 

 

void

foo2 (В2 *Ь2)

/ /

производные объекты содержат

 

 

 

/ /

дополнительные свойства

{ b2->f2();

}

 

 

Глава 15 • Виртуальные функции и использование наследования

void foo(Derivecl *с1)

/ /

базовые объекты не могут выполнить это

{ d->f3(); }

 

 

 

В1 *Ь1 = new Derived;

В2 *Ь2 = new Derived;

 

Derived d;

 

 

 

Foo1(&d);

foo2(&d);

/ /

оба - OK: безопасное преобразование

foo(b1);

foo(b2);

/ /

синтаксические

ошибки: опасное преобразование

foo((Derived*)b1); foo((Derived*)b2);

/ / передается на свой риск

В последнем примере функции fool () и foo2() могут принять объекты Derived как фактические аргументы, поскольку внутри этих функций параметры отвечают только на базовые сообщения (f1() и f2()), а производные объекты — на эти сервисы. Функция foo() не может принимать базовые указатели, потому что внутри нее их параметр должен отвечать на сообщения производного класса f 3(), а базовые объекты этого не могут сделать. С другой стороны, указатели Ь1 и Ь2 ссылаются на объекты класса Derived, которые выполняют такое задание. Чтобы сообщить это компилятору, последняя строка программы, приведенной выше, выполняет явное приведение указателя Base в указатель Derived.

В закрытом или защищенном режиме наследования не допускаются неявные преобразования из объектов производного класса в объекты базового класса. Даже в этом "безопасном" случае требуется явное приведение в клиентской про­ грамме. Преобразование из любого базового класса в производный класс требует явного приведения для любого вида множественного наследования.

Множественное наследование: конструкторы и деструкторы

Производный класс отвечает за состояние своих компонентов, унаследованных от базовых классов. Как и в простом наследовании, конструкторы базового класса вызываются, когда строится объект производного класса.

Механизм передачи параметров конструкторам базового класса подобен меха­ низму для простого наследования. Должен использоваться список инициализации элементов. В следующем примере базовый класс В1 содержит один элемент дан­ ных, базовый класс В2 — другой элемент данных, а производный класс — еще один элемент данных (динамически выделенный массив символов). Класс Derived должен обеспечить для конструктора три параметра, чтобы он мог передать данные своим компонентам В1 и В2 и собственному элементу данных.

class В1 { int m1;

public: B1(int) ;

void fl(); . . . . };

class B2 { double m2;

public:

B2(double);

void f 2 ( ) ; . . . . };

class Derived: public B1, public B2 { char* t;

public:

Derived(const char*, double,int); "DerivedO;

void f3(); ... };

704

Часть IV # Раситреииое использование С^--*-

Если список инициализации элементов не предусматривается, то вызывается конструктор Base по умолчанию. Если базовые классы не имеют в виду конструк­ торы по умолчанию, то это синтаксическая ошибка.

В списке инициализации элементов конструктор класса Derived вызывает базовые конструкторы, используя имена классов В1 и В2 в последовательности вызовов конструкторов, разделенных запятыми. Имена параметров для базовых конструкторов обычно поступают из списков параметров конструктора Derived.

Derived: :Derived(const

char *s, doubled, int i )

: B1(i),B2(d)

{ i f ( ( t = new char[strlen(s)+l] )

== NULL)

 

{ cout «

"\nOut

of memory\n";

e x i t ( l ) ;

}

strcpy(t,s);

}

 

 

 

Bee конструкторы базового класса вызываются до вызова конструкторов про­ изводного класса. Располагаются они в том порядке, в котором базовые классы перечислены в объявлении производного класса.

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

При уничтожении объекта производного класса (динамически или при выходе из области видимости) вначале вызывается деструктор производного класса, а за­ тем деструкторы базового класса в порядке, обратном вызову конструкторов.

Множественное наследование: неоднозначность

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

В следующем примере класс Derived содержит элемент данных х с тем же име­ нем, что и элемент данных в базовом классе В1. Кроме того, класс Derived имеет функцию-член f2() с тем же именем, что и функция-член в базовом классе В2.

class В1 {

 

 

protected:

 

 

int х;

 

/ / скрыто Derived::х

public:

 

 

void f1();

. . . } ;

 

class В2 {

 

 

public:

. . . } ;

// скрыто Derived::f2()

void f2();

class Derived: public B1, public B2 {

protected:

 

// скрывает B1::x

float x;

 

public:

 

// скрывает B2::f2()

void f2();

 

void f3()

 

 

{ X = 0;

}. . .. };

/ / используется Derived: :x

В этом примере объект класса Derived содержит два элемента данных х; эле­ мент данных, наследованный из В1, скрывается в Derived; функция-член f2(), на­ следованная из В2, скрывается добавленной функцией f2().

Как клиентская программа, так и программа класса Derived может подменять правила области видимости, используя .явный оператор области действия.

void Derived::f3()

 

{ Bl: :х = 0; }

/ / игнорируя Derived::х

Глава 15 • Виртуальные функции и использование наследования

| 705 |

Derived d;

 

 

d.f2();

/ /

Der::f2();

d.B2::f2();

/ /

B2::f2();

Конфликты между именем производного класса и именами базового класса случаются не очень часто. Обычно проектировщик производного класса имеет возможность просмотреть структуру базовых классов и избежать конфликтов.

Имена элементов базового класса также могут конфликтовать. С этим бороть­ ся намного сложнее, потому что базовые классы часто разрабатываются незави­ симо друг от друга. Существует небольшая возможность скоординировать их разработку, чтобы избежать конфликтов имен.

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

В следующем примере оба класса включают открытую функцию-член с име­ нем f1(). Это означает, что клиентская программа не может использовать ни один из них, если только она не обеспечивает явного указания, какой использо­ вать.

class

В1 {

 

 

 

 

public:

 

 

 

 

void

f 1 ( ) ; . . . }

;

 

class

82 {

 

 

 

 

public-

 

 

 

 

void

f l ( ) ;

...

}

;

 

class

Derived : public B1, public

B2 {

public:

 

 

 

 

void

f3();

...

}

;

 

Derived d;

 

 

 

// двусмысленное сообщение

d.f1();

d.B2::fl(); d.f3();

d.B1::f1();

// OK

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

Лучше сделать так, чтобы класс Derived изолировал клиентскую программу от неоднозначности имен функции-члена.

class Derived: public В1, public В2 { public:

void f1() { В1: : f1(); } / / однострочники void f2() { B2 : : f1(); }

void f3(); . . . } ;

Derived d;

 

d . f1(); d.f2(); d.f3();

/ / клиентская часть изолирована

Это решение намного лучше. Присутствует серверная программа (класс Derived), которая берет на себя часть обязанностей. Клиентская программа изолирована от проблемы. Когда клиентская программа использует класс Derived как сервер, она должна знать только то, как вызвать сервисы f1(), f2() и f3(), чтобы вы­ полнить задание.

706

Часть IV * Расширенное использование С^+

Если два или более базовых классов содержат элемент данных с одинаковым именем, то объект производного класса включает обе копии. В результате вы столкнетесь с неоднозначностью.

class

В1 {

 

 

protected:

 

 

int

m;

.

public:

 

 

B K i n t ) ;

 

 

void

f ( ) ; ... };

 

class

B2 {

 

 

protected:

 

 

double m;

 

 

public:

 

 

B2(double);

 

 

void

f ( ) ; ...

};

 

class Derived

: public B1, public B2 {

 

char* t;

 

 

public:

 

 

Derived (char*,double,int);

 

void f3() {

cout « "m=" « m « endl; }

/ / двусмысленное выражение

Конфликты между именами элементов данных должны разрешаться производ­ ным классом, чтобы избежать неоднозначности и защитить клиентскую програм­ му. Используйте оператор области действия.

void Derived::f3()

 

{ cout « "m=" « B1: :m « endl; }

/ / двусмысленность отсутствует

Множественное наследование: ориентированный граф

Это наиболее хитрая форма неоднозначности, возникающая, когда базовый класс наследуется более чем из одного класса. Как правило, C++ против этого, а класс может явно появиться только один раз в списке происхождения для произ­ водного класса.

class В { public:

int m; . . . .} ;

class Derived: public B, public В

/ / синтаксическая ошибка

{. . . . } ;

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

впорождениях, и их данные в производных классах будут содержать несколько копий.

class В1

: public

В {

/ / класс В приводится выше

protected:

 

 

int mem;

 

 

public:

 

 

 

void

f1(); ...

};

 

Глава 15 • Виртуальные функции и использование наследования

| 7U7

class В2 : public

В {

/ /

класс В приводится выше

protected:

 

 

 

 

 

int mem;

 

 

 

 

 

public:

 

 

 

 

 

void f2();

...

};

 

 

 

class Derived

: public B1, public B2 {

/ /

унаследовано из В дважды

public:

 

 

 

 

 

void f3();

. .

. } ;

 

 

 

В этой структуре класс Derived содержит два элемента наборов данных с име­ нем mem, унаследованным из разных базовых классов. Имена у них одинаковые, но они указывают на разные положения в памяти. Их роли в программе также от­ личаются: они происходят из разных классов. Эту проблему не следует переносить на клиента.

Ситуация с элементом данных m намного хуже. Каждый объект класса Derived располагает двумя экземплярами этого элемента данных. Один унаследован через класс В1, а другой через класс В2. Пространство, которое требуется* для несколь­ ких экземпляров одного и того же базового элемента данных, тратится напрасно. Эти два элемента данных также функционально одинаковы — они происходят из одного и того же класса, но один из них обслуживает части В1 класса Derived,

авторой — части 82 класса Derived.

Вязыке C-f-h предлагается интересное решение этой задачи. Программисту предоставляется возможность явно указать, что использование двух (или более) копий этих же данных и функций нежелательно. Хотелось бы, чтобы это был случай по умолчанию. Это оговаривается путем определения базовых классов виртуальными базовыми классами. Зарезервированное слово virtual модифи­ цирует объявления производных классов, которые позже используются во мно­ жественном наследовании,

class

В {

 

/ /

общий базовый класс

 

int m;

 

 

 

 

 

public:

 

 

 

 

 

void

f ( ) ; . . . } ;

 

 

 

 

 

class

81 : v i r t u a l

public В {

/ /

виртуальный

базовый

класс

protected:

 

 

 

 

 

int mem;

 

 

 

 

 

public:

 

 

 

 

 

void f 1 ( ) ; . . .

};

 

 

 

 

class 82 : virtual public 8 {

// виртуальный

базовый

класс

protected:

 

 

 

 

 

int mem;

 

 

 

 

 

public:

};

 

 

 

 

void f2(); ...

 

 

 

 

class Derived : public 81, public 82 {

// работает как волшебник

public:

};

 

 

 

 

void f3(); ...

 

 

 

 

Теперь класс Derived содержит только одну копию данных и функций, на­ следованных из класса В. Обратите внимание, что в классе Derived именно про­ ектировщик его базовых классов В1 и В2 должен определить эти классы как виртуальные. Это поставит под сомнение принцип, что базовые классы не знают своих производных классов и лишь производные классы уверены в своих базовых классах.

708

Чость iV # Расширенное тсполь^ованте C-f4«

Не следует путать зарезервированное слово virtual, используемое в данном контексте, с зарезервированным словом virtual, применяемым для виртуальных функций. Они совершенно разные. Было бы прекрасно иметь два разных заре­ зервированных слова. Возможно, лучше, если бы множественное наследование отсутствовало.

Полезно ли множественное наследование

Трудно однозначно ответить на этот вопрос, однако по-видимому сложность структуры с множественным наследованием перевешивает преимуш,ества его ис­ пользования.

Если требуется спроектировать класс, который предоставляет своим клиентам объединение сервисов других классов, используйте составление или составление с наследованием.

Рассмотрим первый пример наследования, который уже обсуждался в начале раздела. Цель этого проекта состоит в обеспечении клиентской программы воз­ можностью вызывать функции f 1 (), f2() и f3(). Функции f 1 () и f2() уже реали­ зованы в классах В1 и В2. Требуется реализовать функцию f3().

class В1

 

 

 

 

{

public:

 

 

 

 

 

void

f1();

/ /

общедоступный сервис f1()

 

. . . } ;

 

/ /

оставшаяся часть

класса В1

class В2

 

 

 

 

{

public:

 

 

 

 

 

void

f2();

/ /

открытый сервис

f2()

 

.. . } ;

 

/ /

оставшаяся часть

класса В2

При использовании

множественного наследования требуемая функция f3()

реализуется в новом классе Derived.

 

 

 

 

 

class Derived : public

В1, public В2

/ /

два базовых

класса

{ public:

 

/ /

f1(),

f2()

наследуются

void f3();

 

/ /

f3()

добавляется

к сервисам

... };

 

/ /

оставшаяся

часть

класса Derived

Вместо этого можно создать функцию f 1(), наследуюшую класс Derived из клас­

са В1. Чтобы

предоставить клиентам класса Derived функцию f2(), поле клас­

са В2 следует

сделать элементом класса Derived.

class Derived : public В1 {

/ /

простое наследование

В2 Ь2;

 

/ /

композиция класса

public:

 

 

 

 

void

f2()

{ b2.f2() ; }

/ /

однострочник

void

f3();

... };

 

 

Теперь клиентская программа может приписать значение объектам Derived и отправить им сообщения точно так же, как и в случае множественного наследо­ вания.

Derived d;

/ /

присваивание значения объекту Derived

d.f1()

/ /

унаследованные сервисы (В1)

d.f2()

/ /

передано из В2 через Derived

d.f3()

/ /

добавлено в класс Derived

Данная клиентская программа не должна рассматриваться как способ проек­ тирования класса Derived. Он предоставляет требуемые сервисы, и это все, что требуется,— без сложностей множественного наследования.

Глава 15 • Виртуальные функции и использование носдвдования

I 709 Р

Итоги

В этой главе рассмотрены примеры расширенного использования наследова­ ния. Все они касались некоторых обидих возможностей базовых и производных классов. В каких-то случаях объекты одного класса могут использоваться вместо объектов другого класса.

Показано, что использование объекта производного класса в случае, когда ожидается объект базового класса, всегда безопасно. Такое преобразование безо­ пасно, но не очень интересно. Эти объекты должны будут выполнить только то, что может сделать базовый объект, а объект производного класса способен на большее.

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

В языках программирования это всегда было проблемой. Все совокупности объектов, которые поддерживаются современными языками, являются однород­ ными. Массивы С+ 4- не могут содержать компоненты различных классов. Связанные списки С4-+ не могут использовать узлы разных типов. И только на­ следование позволяет применять совокупности объектов разных классов. Такие классы не являются совершенно разными. Неоднородные списки не содержат объекты произвольных классов, но могут включать объекты классов, связанные наследованием.

При обработке неоднородной совокупности объектов (связанных наследова­ нием) объектам в совокупности отправляются четыре типа сообпдений.

Сообш,ения, на которые может ответить каждый объект

всовокупности объектов. Методы, определенные в базовом классе иерархии наследования, которые не перезаписываются

впроизводных классах.

Сообнхения, на которые могут ответить только некоторые объекты

всовокупности объектов. Методы, определенные в производных классах

иерархии наследования, за исключением сообш^ений с теми же именами

вбазовом классе.

Сообш,ения, на которые могут ответить объекты с именами всех типов

всовокупности объектов, но определенные в базовых и производных классах как невиртуальные функции (с тем же самым или

сдругим интерфейсом).

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

Для доступа к первому типу сообш,ения используйте указатель базового класса. Когда доступ к объекту осуш^ествляется из совокупности объектов, не требуется никаких преобразований.

Отправить второй вид сообш.ений можно с помои;ью указателей производного класса. Когда объект берется из совокупности объектов, базовый указатель должен преобразовываться в указатели того класса, к которому принадлежит объект. Только тогда будут доступны сообщения второго вида. Это преобразова­ ние не является безопасным, и необходимо хорошо представлять себе, что проис­ ходит, потому что компилятор не в состоянии защитить вас.

Третий вид сообщений также требует преобразования, если объект должен отвечать на сообщение, определенное в производном классе. Сообщение базового класса скрывается сообщением производного класса.

Соседние файлы в предмете Программирование на C++