Читаем Программирование полностью

Итак, если объект x относится к классу Circle, будет вызвана функция Circle::draw_lines(). Если объект x относится к типу, скажем, Open_polyline, который использует таблицу vtbl точно в том виде, в каком ее определил класс Shape, то будет вызвана функция Shape::draw_lines(). Аналогично, поскольку в классе Circle не определена его собственная функция move(), при вызове x.move() будет выполнена функция Shape::move(), если объект x относится к классу Circle. В принципе код, сгенерированный для вызова виртуальной функции, может просто найти указатель vptr и использовать его для поиска соответствующей таблицы vtbl и вызова нужной функции оттуда. Для этого понадобятся два обращения к памяти и обычный вызов функции, — быстро и просто.

Класс Shape является абстрактным, поэтому мы не можем на самом деле непосредственно создать объект класса Shape, но класс Open_polyline имеет точно такую же простую структуру, поскольку не добавляет никаких данных-членов и не определяет виртуальную функцию. Таблица виртуальных функций vtbl определяется для каждого класса, в котором определена виртуальная функция, а не для каждого объекта, поэтому таблицы vtbl незначительно увеличивают размер программы.

Обратите внимание на то, что на рисунке мы не изобразили ни одной невиртуальной функции. В этом не было необходимости, поскольку об этих функциях мы не можем сказать что-то особенное и они не увеличивают размеры объектов своего класса. Определение функции, имеющей то же имя и те же типы аргументов, что и виртуальная функция из базового класса (например, Circle::draw_lines()), при котором функция из производного класса записывается в таблицу vtbl вместо соответствующей функции из базового класса, называется замещением (overriding). Например, функция Circle::draw_lines() замещает функцию Shape::draw_lines().

Почему мы говорим о таблицах vtbl и схемах размещения в памяти? Нужна ли нам эта информация, чтобы использовать объектно-ориентированное программирование? Нет. Однако многие люди очень хотят знать, как устроены те или иные механизмы (мы относимся к их числу), а когда люди чего-то не знают, возникают мифы. Мы встречали людей, которые боялись использовать виртуальные функции, “потому что они повышают затраты”. Почему? Насколько? По сравнению с чем? Как оценить эти затраты? Мы объяснили модель реализации виртуальных функций, чтобы вы их не боялись. Если вам нужно вызвать виртуальную функцию (для выбора одной из нескольких альтернатив в ходе выполнения программы), то вы не сможете запрограммировать эту функциональную возможность с помощью другого языкового механизма, который работал бы быстрее или использовал меньше памяти, чем механизм виртуальных функций. Можете сами в этом убедиться. 

14.3.2. Вывод классов и определение виртуальных функций

Мы указываем, что класс является производным, упоминая базовый класс перед его именем. Рассмотрим пример.


struct Circle:Shape { /* ... */ }; 


  По умолчанию члены структуры, объявляемой с помощью ключевого слова struct, являются открытыми (см. раздел 9.3) и наследуют открытые члены класса. Можно было бы написать эквивалентный код следующим образом:


class Circle : public Shape { public: /* ... */ };


Эти два объявления класса Circle совершенно эквивалентны, но вы можете провести множество долгих и бессмысленных споров о том, какой из них лучше. Мы считаем, что время, которое можно затратить на эти споры, лучше посвятить другим темам.

Не забудьте указать слово public, когда захотите объявить открытые члены класса. Рассмотрим пример.


class Circle : Shape { public: /* ... */ }; // возможно, ошибка


В этом случае класс Shape считается закрытым базовым классом для класса Circle, а открытые функции-члены класса Shape становятся недоступными для класса Circle. Вряд ли вы стремились к этому. Хороший компилятор предупредит вас о возможной ошибке. Закрытые базовые классы используются, но их описание выходит за рамки нашей книги.

Виртуальная функция должны объявляться с помощью ключевого слова virtual в объявлении своего класса, но если вы разместили определение функции за пределами класса, то ключевое слово virtual указывать не надо.


struct Shape {

  // ...

  virtual void draw_lines() const;

  virtual void move();

  // ...

};


  virtual void Shape::draw_lines() const { /* ... */ } // ошибка

  void Shape::move() { /* ... */ } // OK

14.3.3. Замещение

Перейти на страницу:
Нет соединения с сервером, попробуйте зайти чуть позже