Вход/Регистрация
Программирование. Принципы и практика использования C++ Исправленное издание
вернуться

Страуструп Бьерн

Шрифт:

• Виртуальные функции. В языке С++ можно определить функцию в базовом классе и функцию в производном классе с точно таким же именем и типами аргументов, чтобы при вызове пользователем функции базового класса на самом деле вызывалась функция из производного класса. Например, когда класс Window вызывает функцию

draw_lines
из класса
Circle
, выполняется именно функция
draw_lines
из класса
Circle
, а не функция
draw_lines
из класса
Shape
. Это свойство часто называют динамическим полиморфизмом (run-time polymorphism) или динамической диспетчеризацией (run-time dispatch), потому что вызываемые функции определяются на этапе выполнения программы по типу объекта, из которого они вызываются.

• Закрытые и защищенные члены. Мы закрыли детали реализации наших классов, чтоб защитить их от непосредственного доступа, который может затруднить сопровождение программы. Это свойство часто называют инкапсуляцией (encapsulation).

Наследование, динамический полиморфизм и инкапсуляция — наиболее распространенные характеристики объектно-ориентированного программирования (object-oriented programming). Таким образом, язык C++ непосредственно поддерживает объектно-ориентированное программирование наряду с другими стилями программирования. Например, в главах 20-21 мы увидим, как язык C++ поддерживает обобщенное программирование. Язык C++ позаимствовал эти ключевые механизмы из языка Simula67, первого языка, непосредственно поддерживавшего объектно-ориентированное программирование (подробно об этом речь пойдет в главе 22).

Довольно много технической терминологии! Но что все это значит? И как на самом деле эти механизмы работают? Давайте сначала нарисуем простую диаграмму наших классов графического интерфейса, показав их отношения наследования.

Стрелки направлены от производного класса к базовому. Такие диаграммы помогают визуализировать отношения между классами и часто украшают доски программистов. По сравнению с коммерческими пакетами эта иерархия классов невелика и содержит всего шестнадцать элементов. Причем в этой иерархии только класс

Open_polyline
имеет несколько поколений наследников. Очевидно, что наиболее важным является общий базовый класс (
Shape
), несмотря на то, что он представляет абстрактное понятие о фигуре и никогда не используется для ее непосредственного воплощения.

14.3.1. Схема объекта

Как объекты размещаются в памяти? Как было показано в разделе 9.4.1, схема объекта определяется членами класса: данные-члены хранятся в памяти один за другим. Если используется наследование, то данные-члены производного класса просто добавляются после членов базового класса. Рассмотрим пример.

Класс

Circle
имеет данные-члены класса
Shape
(в конце концов, он является разновидностью класса
Shape
) и может быть использован вместо класса
Shape
. Кроме того, класс
Circle
имеет свой собственный член
r
, который размещается в памяти после унаследованных данных-членов.

Для того чтобы обработать вызов виртуальной функции, нам нужна еще одна порция данных в объекте класса
Shape
: информация о том, какая функция будет на самом деле вызываться при обращении к функции
draw_lines
из класса
Shape
. Для этого обычно в таблицу функций заносится ее адрес. Эта таблица обычно называется
vtbl
(таблица виртуальных функций), а ее адрес часто имеет имя
vptr
(виртуальный указатель). Указатели обсуждаются в главах 17-18; здесь они действуют как ссылки. В конкретных реализациях языка таблица виртуальных функций и виртуальный показатель могут называться иначе. Добавив таблицу
vptr
и указатели
vtbl
к нашему рисунку, получим следующую диаграмму.

Поскольку функция

draw_lines
— первая виртуальная функция, она занимает первую ячейку в таблице
vtbl
, за ней следует функция
move
, вторая виртуальная функция. Класс может иметь сколько угодно виртуальных функций; его таблица
vtbl
может быть сколь угодно большой (по одной ячейке на каждую виртуальную функцию). Теперь, когда мы вызовем функцию
x.draw_lines
, компилятор сгенерирует вызов функции, найденной в ячейке
draw_lines
таблицы
vtbl
, соответствующей объекту
x
. В принципе код просто следует по стрелкам на диаграмме.

Итак, если объект

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
незначительно увеличивают размер программы.

  • Читать дальше
  • 1
  • ...
  • 218
  • 219
  • 220
  • 221
  • 222
  • 223
  • 224
  • 225
  • 226
  • 227
  • 228
  • ...

Ебукер (ebooker) – онлайн-библиотека на русском языке. Книги доступны онлайн, без утомительной регистрации. Огромный выбор и удобный дизайн, позволяющий читать без проблем. Добавляйте сайт в закладки! Все произведения загружаются пользователями: если считаете, что ваши авторские права нарушены – используйте форму обратной связи.

Полезные ссылки

  • Моя полка

Контакты

  • chitat.ebooker@gmail.com

Подпишитесь на рассылку: