Программирование. Принципы и практика использования C++ Исправленное издание - Бьёрн Страуструп
Шрифт:
Интервал:
Закладка:
void f(Simple_window& w)
{
Rectangle r(Point(100,200),50,30);
w.attach(r);
} // Ой, объекта r больше нет
int main()
{
Simple_window win(Point(100,100),600,400,"Мое окно");
// ...
f(win); // возникают проблемы
// ...
win.wait_for_button();
}
Пока мы выходили из функции f() и входили в функцию wait_for_button(), объект r для объекта win перестал существовать и соответственно выводиться на экран. В главе 17 мы покажем, как создать объект в функции и сохранить его между ее вызовами, а пока должны избежать связывания с объектом, который исчез до вызова функции wait_for_button(). Для этого можно использовать класс Vector_ref, который рассматривается в разделах 14.10 и Г.4.
Обратите внимание на то, что если бы мы объявили функцию f() так, чтобы она получала константную ссылку на объект класса Window (как было рекомендовано в разделе 8.5.6), то компилятор предотвратил бы ошибку: мы не можем выполнить вызов attach(r) с аргументом типа const Window, поскольку функция attach() должна изменить объект класса Window, чтобы зарегистрировать связь между ним и объектом r.
14.1.4. Изменяемость
Основные вопросы, на которые следует ответить, проектируя классы, звучат так: кто может модифицировать данные и как он может это делать? Мы должны гарантировать, что изменение состояния объекта будет осуществляться только членами его класса. Именно для этого предназначены разделы public и private, но мы продемонстрируем еще более гибкий и тонкий механизм, основанный на ключевом слове protected. Это значит, что мы не можем просто включить в класс какой-то член, скажем, переменную label типа string; мы должны также решить, следует ли открыть его для изменений после создания объекта, и если да, то как. Мы должны также решить, должен ли другой код, кроме данного класса, иметь доступ к переменной label, и если да, то как. Рассмотрим пример.
struct Circle {
// ...
private:
int r; // radius
};
Circle c(Point(100,200),50);
c.r = –9; // OK? Нет — ошибка компилирования: переменная Circle::r
// закрыта
Как указано в главе 13, мы решили предотвратить прямой доступ к большинству данных-членов класса. Это дает нам возможность проверять “глупые” значения, например отрицательные радиусы у объектов класса Circle. Для простоты реализации мы не проводим полную проверку, поэтому будьте осторожны, работая с числами. Мы отказались от полной и последовательной проверки, желая уменьшить объем кода и понимая, что если пользователь введет “глупое” значение, то ранее введенные данные от этого не пострадают, просто на экране появится искаженное изображение.
Мы интерпретируем экран (т.е. совокупность объектов класса Window) исключительно как устройство вывода. Мы можем выводить новые объекты и удалять старые, но никогда не обращаемся к системе за информацией, которую сами не можем извлечь из структур данных, на основе которых строится изображение.
14.2. Класс Shape
Класс Shape отражает общее понятие о том, что может изображаться в объекте класса Window на экране.
• Понятие, которое связывает графические объекты с нашей абстракцией Window, которая в свою очередь обеспечивает связь с операционной системой и физическим экраном.
• Класс, работающий с цветом и стилем, используемыми при рисовании линий. Для этого он хранит члены классов Line_style и Color (для линий и заполнения).
• Может хранить последовательности объектов класса Point и информацию о том, как их рисовать.
Опытные проектировщики отметят, что класс, обладающий только этими тремя свойствами, может иметь недостаточно общий характер. Однако мы описываем решение, которое очень далеко от общего.
Сначала опишем полный класс, а затем подробно его обсудим.
class Shape { // работает с цветом и стилем, хранит последователь -
// ность точек
public:
void draw() const; // работает с цветом и рисует линии
virtual void move(int dx, int dy); // перемещает фигуры +=dx
// и +=dy
void set_color(Color col);
Color color() const;
void set_style(Line_style sty);
Line_style style() const;
void set_fill_color(Color col);
Color fill_color() const;
Point point(int i) const; // доступ к точкам только для чтения
int number_of_points() const;
virtual ~Shape() { }
protected:
Shape();
virtual void draw_lines() const; // рисует линии
void add(Point p); // добавляет объект p к точкам
void set_point(int i, Point p); // points[i]=p;
private:
vector<Point> points; // не используется всеми фигурами
Color lcolor; // цвет для линий и символов
Line_style ls;
Color fcolor; // заполняет цветом
Shape(const Shape&); // копирующий конструктор
Shape& operator=(const Shape&);
};
Это относительно сложный класс, разработанный для поддержки работы множества графических классов и представления общего понятия о фигуре на экране. Однако в нем всего четыре данных-членов и пятнадцать функций. Более того, эти функции почти все тривиальны, так что мы можем сосредоточиться на вопросах проектирования. В оставшейся части главы мы пройдемся по всем членам шаг за шагом и объясним их роль в классе.
14.2.1. Абстрактный класс
Сначала рассмотрим конструктор класса Shape:
protected:
Shape();
который находится в разделе protected. Это значит, что его можно непосредственно использовать только в классах, производных от класса Shape (используя обозначение :Shape). Иначе говоря, класс Shape можно использовать только в качестве базы для других классов, таких как Line и Open_polyline. Цель ключевого слова protected: — гарантировать, что мы не сможем создать объекты класса Shape непосредственно.
Рассмотрим пример.
Shape ss; // ошибка: невозможно создать объект класса Shape
Класс Shape может быть использован только в роли базового класса. В данном случае ничего страшного не произошло бы, если бы мы позволили создавать объекты класса Shape непосредственно, но, ограничив его применение, мы