Логически разные операции имеют разные имена. И опять-таки, несмотря на то, что это очевидно, существуют вопросы: почему мы связываем объект класса Shape
Window
, но добавляем объект класса Line
к объекту класса Shape
? В обоих случаях мы “помещаем нечто во что-то”, так почему бы не назвать такие операции одинаково? Нет. За этой схожестью кроется фундаментальная разница. Рассмотрим пример.Open_polyline opl;
opl.add(Point(100,100));
opl.add(Point(150,200));
opl.add(Point(250,250));
Здесь мы копируем три точки в объект opl
opl
безразлично, что будет с нашими точками после вызова функции add()
; она хранит свои собственные копии этих точек. На самом деле мы редко храним копии точек, а просто передаем их фигуре. С другой стороны, посмотрим на следующую инструкцию:win.attach(opl);
Здесь мы создаем связь между окном win и нашей фигурой opl
win
не создает копию объекта opl
, а вместо этого хранит ссылку на него. Итак, мы должны обеспечить корректность объекта opl
, поскольку объект win
использует его. Иначе говоря, когда окно win
использует фигуру opl
, оно должно находиться в ее области видимости. Мы можем обновить объект opl
, и в следующий раз объект win
будет рисовать фигуру opl
с изменениями. Разницу между функциями attach()
и add()
можно изобразить графически.Функция add()
attach()
— механизм передачи параметров по ссылке (использует общий объект). Мы могли бы решить копировать графические объекты в объекты класса Window
. Однако это была бы совсем другая модель программирования, которая определяется выбором функции add()
, а не attach()
. Мы решили просто связать графический объект с объектом класса Window
. Это решение имеет важные последствия. Например, мы не можем создать объект, связать его, позволить его уничтожить и ожидать, что программа продолжит работать.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
// закрыта