Вход/Регистрация
Эффективное использование C++. 55 верных способов улучшить структуру и код ваших программ
вернуться

Майерс Скотт

Шрифт:

r.setWidth(r.width + 10); // увеличить ширину r на 10

assert(r.height == oldHeight); // убедиться, что высота r

} // не изменилась

Ясно, что утверждение assert никогда не должно нарушаться. Функция make-Bigger изменяет только ширину r. Высота остается постоянной.

Теперь рассмотрим код, который посредством открытого наследования позволяет рассматривать квадрат как частный случай прямоугольника:

class Square: public Rectangle {…};

Square s;

...

assert(s.width == s.height); // должно быть справедливо для

// всех квадратов

makeBigger(s); // из-за наследования, s является

// Rectangle, поэтому мы можем

// увеличить его площадь

assert(s.width == s.height); // По-прежнему должно быть справедливо

// для всех квадратов

Как и в предыдущем примере, что второе утверждение также никогда не должно быть нарушено. По определению, ширина квадрата равна его высоте.

Но теперь перед нами встает проблема. Как примирить следующие утверждения?

• Перед вызовом makeBigger высота s равна ширине.

• Внутри makeBigger ширина s изменяется, а высота – нет.

• После возврата из makeBigger высота s снова равна ширине (отметим, что s передается по ссылке, поэтому makeBigger модифицирует именно s, а не его копию).

Так что же?

Добро пожаловать в удивительный мир открытого наследования, где интуиция, приобретенная вами в других областях знания, включая математику, иногда оказывается плохим помощником. Основная трудность в данном случае заключается в том, что некоторые утверждения, справедливые для прямоугольника (его ширина может быть изменена независимо от высоты), не выполняются для квадрата (его ширина и высота должны быть одинаковы). Но открытое наследование предполагает, что все, что применимо к объектам базового класса, – все! – также применимо и к объектам производных классов. В ситуации с прямоугольниками и квадратами (а также в аналогичных случаях, включая множества и списки из правила 38), утверждение этого условия не выполняется, поэтому использование открытого наследования для моделирования здесь некорректно. Компилятор, конечно, этого не запрещает, но, как мы только что видели, не существует гарантий, что такой код будет вести себя должным образом. Любому программисту должно быть известно (некоторые знают это лучше других): если код компилируется, то это еще не значит, что он будет работать.

Все же не стоит беспокоиться, что приобретенная вами за многие годы разработки программного обеспечения интуиция окажется бесполезной при переходе к объектно-ориентированному программированию. Все ваши знания по-прежнему актуальны, но теперь, когда вы добавили к своему арсеналу наследование, вам придется дополнить свою интуицию новым пониманием, позволяющим создавать приложения с использованием наследования. Со временем идея наследования Penguin от Bird или 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.

  • Читать дальше
  • 1
  • ...
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • ...

Ебукер (ebooker) – онлайн-библиотека на русском языке. Книги доступны онлайн, без утомительной регистрации. Огромный выбор и удобный дизайн, позволяющий читать без проблем. Добавляйте сайт в закладки! Все произведения загружаются пользователями: если считаете, что ваши авторские права нарушены – используйте форму обратной связи.

Полезные ссылки

  • Моя полка

Контакты

  • chitat.ebooker@gmail.com

Подпишитесь на рассылку: