или Square от Rectangle будет казаться вам столь же забавной, как функция объемом в несколько страниц. Такое решение может оказаться правильным, но это маловероятно.

Отношение «является» – не единственное, возможное между классами. Два других, достаточно распространенных отношения – это «содержит» и «реализован посредством». Они рассматриваются в правилах 38 и 39. Очень часто при проектировании на C++ весь проект идет вкривь и вкось из-за того, что эти взаимосвязи моделируются отношением «является». Поэтому вы должны быть уверены, что понимаете различия между этими отношениями и знаете, каким образом их лучше всего моделировать в C++.

Что следует помнить

• Открытое наследование означает «является». Все, что применимо к базовому классу, должно быть применимо также и производным от него, потому что каждый объект производного класса является также объектом базового класса.

Правило 33: Не скрывайте унаследованные имена

Шекспир много размышлял об именах. Он писал: «Что в имени тебе? Роза пахнет розой, хоть розой назови ее, хоть нет». И еще писал бард: «Кто доброе мое похитит имя, несчастным сделает меня вовек…» Правильно. И это заставляет нас обратить взор на унаследованные имена в C++.

Вообще-то эта тема относится не столько к наследованию, сколько к областям видимости. Все мы знаем, что в таком коде:

int x; // глобальная переменная

void someFunc()

{

double x; // локальная переменная

std::cin >> x; // прочитать новое значение локальной переменной x

}

имя x в предложении считывания относится к локальной, а не к глобальной переменной, потому что имена во вложенной области видимости скрывают («затеняют») имена из внешних областей. Мы можем представить эту ситуацию визуально:

Когда компилятор встречает имя x внутри функции someFunc, он смотрит, определено ли что-то с таким именем в локальной области видимости. Если да, то объемлющие области видимости не просматриваются. В данном случае имя x в функции someFunc принадлежит переменной типа double, а глобальная переменная с тем же именем x имеет тип int, но это несущественно. Правила сокрытия имен в C++ предназначены для одной-единственной цели: скрывать имена. Относятся ли одинаковые имена к объектам одного или разных типов, не имеет значения. В нашем примере переменная x типа double скрывает переменную x типа int.

Вернемся к наследованию. Мы знаем, что когда находимся внутри функции-члена производного класса и ссылаемся на что-то из базового класса (например, функцию-член, typedef или член данных), компилятор сможет найти то, на что мы ссылаемся, потому что производные классы наследуют свойства, объявленные в базовых классах. Механизм основан на том, что область видимости производного класса вложена в область видимости базового класса. Например:

class Base {

private:

int x;

public:

virtual void mf1() = 0;

virtual void mf2();

void mf3();

...

};

class Derived: public Base {

public:

virtual void mf1()

void mf4();

...

};

В этом примере встречаются как открытые, так и закрытые имена, как имена членов данных, так и функций-членов. Одна из функций-членов – чисто виртуальная, другая – просто виртуальная, а третья – невиртуальная. Это я к тому, что мы говорим именно об именах, а не о чем-то другом. Я мог бы включить в пример еще имена типов, например перечислений, вложенных классов и typedef. В данном контексте важно лишь то, что все это имена. Что они именуют – несущественно. В примере используется одиночное наследование, но, поняв, что происходит при одиночном наследовании, легко будет разобраться и в том, как C++ ведет себя при множественном наследовании.

Предположим, что функция-член mf4 в производном классе реализована примерно так:

void Derived::mf4()

{

...

mf2();

...

}

Когда компилятор видит имя mf2, он должен понять, на что оно ссылается. Для этого в различных областях видимости производится поиск имени mf2. Сначала оно ищется в локальной области видимости (то есть внутри mf4), но там такого имени нет. Тогда просматривается объемлющая область видимости, то есть область видимости класса Derived. И здесь такое имя отсутствует, поэтому компилятор переходит к следующей область видимости, которой является базовый класс. И находит там нечто по имени mf2, после чего поиск завершается. Если бы mf2 не было и в классе Base, то поиск продолжился бы сначала в пространстве имен, содержащем Base, если таковое имеется, и, наконец, в глобальной области видимости.

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

Снова вернемся к предыдущему примеру, но на этот раз перегрузим функции mf1 и mf3, а также добавим версию mf3 в класс Derived. Как объясняется в правиле 36, перегрузка mf3 в производном классе Derived (когда наследуется невиртуальная функция) сама по себе подозрительна, но чтобы лучше разобраться с видимостью имен, закроем на это глаза.

class Base {

private:

int x;

public:

virtual void mf1() = 0;

virtual void mf1(int);

virtual void mf2();

void mf3();

void mf3(double);

...

};

class Derived: public Base {

public:

virtual void mf1()

void mf3();

Добавить отзыв
ВСЕ ОТЗЫВЫ О КНИГЕ В ИЗБРАННОЕ

0

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

Отметить Добавить цитату