Программирование. Принципы и практика использования C++ Исправленное издание, стр. 193

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()
в нашем пользовательском коде запускает драйвер экрана.

Программирование. Принципы и практика использования C++ Исправленное издание - _150.png

Что делает функция

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. Копирование и изменчивость

 

Программирование. Принципы и практика использования C++ Исправленное издание - _002.png
 Класс 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 закрыто

 

Программирование. Принципы и практика использования C++ Исправленное издание - _001.png
 Однако копирование может быть полезным во многих ситуациях! Просто взгляните на функцию
push_back()
; без копирования было бы трудно использовать векторы (функция
push_back()
помещает в вектор копию своего аргумента). Почему надо беспокоиться о непредвиденном копировании? Если операция копирования по умолчанию может вызывать проблемы, ее следует запретить. В качестве основного примера такой проблемы рассмотрим функцию
my_fct()
. Мы не можем копировать объект класса
Circle
в вектор
v
, содержащий объекты типа
Shape
; объект класса
Circle
имеет радиус, а объект класса
Shape
— нет, поэтому
sizeof(Shape) <sizeof(Circle)
. Если бы мы допустили операцию
v.push_back(c)
, то объект класса
Circle
был бы “обрезан” и любое последующее использование элемента вектора
v
привело бы к краху; операции класса
Circle
предполагают наличие радиуса (члена
r
), который не был скопирован.