Выбрать главу

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

Упражнение 18.6

Объясните, в каких случаях имеет место наследование типа, а в каких – наследование реализации:

(a) Queue : List // очередь : список

(b) EncryptedString : String // зашифрованная строка : строка

(c) Gif : FileFormat

(d) Circle : Point // окружность : точка

(e) Dqueue : Queue, List

(f) DrawableGeom : Geom, Canvas // рисуемая фигура : фигура, холст

Упражнение 18.7

Замените член IntArray в реализации PeekbackStack (см. раздел 18.3.1) на класс deque из стандартной библиотеки. Напишите небольшую программу для тестирования.

Упражнение 18.8

Сравните композицию по ссылке с композицией по значению, приведите примеры их использования.

18.4. Область видимости класса и наследование

У каждого класса есть собственная область видимости, в которой определены имена членов и вложенные типы (см. разделы 13.9 и 13.10). При наследовании область видимости производного класса вкладывается в область видимости непосредственного базового. Если имя не удается разрешить в области видимости производного класса, то поиск определения продолжается в области видимости базового.

Именно эта иерархическая вложенность областей видимости классов при наследовании и делает возможным обращение к именам членов базового класса так, как если бы они были членами производного. Рассмотрим сначала несколько примеров одиночного наследования, а затем перейдем к множественному. Предположим, есть упрощенное определение класса ZooAnimaclass="underline"

class ZooAnimal {

public:

ostream &print( ostream& ) const;

// сделаны открытыми только ради демонстрации разных случаев

string is_a;

int ival;

private:

double dval;

};

и упрощенное определение производного класса Bear:

class Bear : public ZooAnimal {

public:

ostream &print( ostream& ) const;

// сделаны открытыми только ради демонстрации разных случаев

string name;

int ival;

};

Когда мы пишем:

Bear bear;

bear.is_a;

* то имя разрешается следующим образом: bear – это объект класса Bear. Сначала поиск имени is_a ведется в области видимости Bear. Там его нет.

* Поскольку класс Bear производный от ZooAnimal, то далее поиск is_a ведется в области видимости последнего. Обнаруживается, что имя принадлежит его члену. Разрешение закончилось успешно.

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

bear.ival;

ival – это член класса Bear, найденный на первом шаге описанного выше процесса разрешения имени.

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

bear.ZooAnimaclass="underline" :ival;

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

Проиллюстрируем использование оператора разрешения области видимости на несколько абсурдном примере (надеемся, вы никогда не напишете чего-либо подобного в реальном коде):

int ival;

int Bear::mumble( int ival )

{

return ival + // обращение к параметру

::ival + // обращение к глобальному объекту

ZooAnimaclass="underline" :ival +

Bear::ival;

}

Неквалифицированное обращение к ival разрешается в пользу формального параметра. (Если бы переменная ival не была определена внутри mumble(), то имел бы место доступ к члену класса Bear. Если бы ival не была определена и в Bear, то подразумевался бы член ZooAnimal. А если бы ival не было и там, то речь шла бы о глобальном объекте.)

Разрешение имени члена класса всегда предшествует выяснению того, является ли обращение к нему корректным. На первый взгляд, это противоречит интуиции. Например, изменим реализацию mumble():