Архитектура Аудит Военная наука Иностранные языки Медицина Металлургия Метрология
Образование Политология Производство Психология Стандартизация Технологии


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



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

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

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.ZooAnimal:: ival;

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

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

int ival;

 

int Bear:: mumble( int ival )

{

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

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

   ZooAnimal:: ival +

   Bear:: ival;

}

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

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

int dval;

int Bear:: mumble( int ival )

{

// ошибка: разрешается в пользу закрытого члена ZooAnimal:: dval

return ival + dval;

}

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

(a)Определено ли dval в локальной области видимости функции-члена класса Bear? Нет.

(b)Определено ли dval в области видимости Bear? Нет.

(c)Определено ли dval в области видимости ZooAnimal? Да. Обращение разрешается в пользу этого имени.

После того как имя разрешено, компилятор проверяет, возможен ли доступ к нему. В данном случае нет: dval является закрытым членом, и прямое обращение к нему из mumble() запрещено. Правильное (и, возможно, имевшееся в виду) разрешение требует явного употребления оператора разрешения области видимости:

return ival +:: dval; // правильно

Почему же имя члена разрешается перед проверкой уровня доступа? Чтобы предотвратить тонкие изменения семантики программы в связи с совершенно независимым, казалось бы, изменением уровня доступа к члену. Рассмотрим, например, такой вызов:

int dval;

int Bear:: mumble( int ival )

{

foo( dval );

//...

}

Если бы функция foo() была перегруженной, то перемещение члена ZooAnimal:: dval из закрытой секции в защищенную вполне могло бы изменить всю последовательность вызовов внутри mumble(), а разработчик об этом даже и не подозревал бы.

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

ostream& Bear:: print( ostream & os) const

{

// вызывается ZooAnimal:: print(os)

ZooAnimal:: print( os );

 

os < < name;

return os;

}


Поделиться:



Последнее изменение этой страницы: 2019-04-09; Просмотров: 392; Нарушение авторского права страницы


lektsia.com 2007 - 2024 год. Все материалы представленные на сайте исключительно с целью ознакомления читателями и не преследуют коммерческих целей или нарушение авторских прав! (0.017 с.)
Главная | Случайная страница | Обратная связь