struct Shape {
// ...
virtual void draw_lines() const;
// пусть каждый производный класс
// сам определяет свою собственную функцию draw_lines(),
// если это необходимо
// ...
};
struct Circle : Shape {
// ...
void draw_lines() const; // " замещение " функции
// Shape::draw_lines()
// ...
};
Итак, функция
draw_lines()
из класса
Shape
должна как-то вызывать одну из функций-членов класса
Circle
, если фигурой является объект класса
Shape
, и одну из функций-членов класса
Rectangle
, если фигура является объектом класса
Rectangle
. Вот что означает слово
virtual
в объявлении функции
draw_lines()
: если класс является производным от класса
Shape
, то он должен самостоятельно объявить свою собственную функцию
draw_lines()
(с таким же именем, как функция
draw_lines()
в классе
Shape
), которая будет вызвана вместо функции
draw_lines()
из класса. В главе 13 показано, как это сделано в классах
Text
,
Circle
,
Closed_polyline
и т.д. Определение функции в производном классе, используемой с помощью интерфейса базового класса, называют
замещением (overriding).
Обратите внимание на то, что, несмотря на свою главную роль в классе
Shape
, функция
draw_lines()
находится в разделе
protected
. Это сделано не для того, чтобы подчеркнуть, что она предназначена для вызова “общим пользователем” — для этого есть функция
draw()
. Просто тем самым мы указали, что функция
draw_lines()
— это “деталь реализации”, используемая функцией
draw()
и классами, производными от класса
Shape
.
На этом завершается описание нашей графической модели, начатое в разделе 12.2. Система, управляющая экраном, “знает” о классе
Window
. Класс
Window
“знает” о классе
Shape
и может вызывать его функцию-член
draw()
. В заключение функция
draw()
вызывает функцию
draw_lines()
, чтобы нарисовать конкретную фигуру. Вызов функции
gui_main()
в нашем пользовательском коде запускает драйвер экрана.
Что делает функция
gui_main()
? До сих пор мы не видели ее в нашей программе. Вместо нее мы использовали функцию
wait_for_button()
, которая вызывала драйвер экрана более простым способом.
Функция
move()
класса
Shape
просто перемещает каждую хранимую точку на определенное расстояние относительно текущей позиции.
void Shape::move(int dx, int dy) // перемещает фигуру +=dx and +=dy
{
for (int i = 0; i<points.size(); ++i) {
points[i].x+=dx;
points[i].y+=dy;
}
}
Подобно функции
draw_lines()
, функция
move()
является виртуальной, поскольку производный класс может иметь данные, которые необходимо переместить и о которых может “не знать” класс
Shape
. В качестве примера можно привести класс
Axis
(см. разделы 12.7.3 и 15.4).
Функция
move()
не является логически необходимой для класса
Shape
; мы ввели ее для удобства и в качестве примера еще одной виртуальной функции. Каждый вид фигуры, имеющей точки, не хранящиеся в базовом классе
Shape
, должен определить свою собственную функцию
move()
.
14.2.4. Копирование и изменчивость
Класс Shape содержит закрытые объявления
копирующего конструктора (copy constructor) и
оператора копирующего присваивания (copy assignment constructor).
private:
Shape(const Shape&); // prevent copying
Shape& operator=(const Shape&);
В результате только члены класса
Shape
могут копировать объекты класса
Shape
, используя операции копирования, заданные по умолчанию. Это общая идиома, предотвращающая непредвиденное копирование. Рассмотрим пример.
void my_fct(const Open_polyline& op, const Circle& c)
{
Open_polyline op2 = op; // ошибка: копирующий конструктор
// класса Shape закрыт
vector<Shape> v;
v.push_back(c); // ошибка: копирующий конструктор
// класса Shape закрыт
// ...
op = op2; // ошибка: присваивание в классе
// Shape закрыто
}
Однако копирование может быть полезным во многих ситуациях! Просто взгляните на функцию
push_back()
; без копирования было бы трудно использовать векторы (функция
push_back()
помещает в вектор
копию своего аргумента). Почему надо беспокоиться о непредвиденном копировании? Если операция копирования по умолчанию может вызывать проблемы, ее следует запретить. В качестве основного примера такой проблемы рассмотрим функцию
my_fct()
. Мы не можем копировать объект класса
Circle
в вектор
v
, содержащий объекты типа
Shape
; объект класса
Circle
имеет радиус, а объект класса
Shape
— нет, поэтому
sizeof(Shape) <sizeof(Circle)
. Если бы мы допустили операцию
v.push_back(c)
, то объект класса
Circle
был бы “обрезан” и любое последующее использование элемента вектора
v
привело бы к краху; операции класса
Circle
предполагают наличие радиуса (члена
r
), который не был скопирован.