2 семестр / Литература / Язык программирования С++. Краткий курс. Страуструп
.pdf90
Глава
4.
Классы
Стрелки
указывают
отношение
наследования.
Например,
класс
Circle
яв
ляется
производным
от класса
Shape.
Иерархия
классов
обычно
изображает
ся
растущей
вниз
от
"самого
базового"
класса
-
от
корня
к
(определяемым
позже)
производным
классам.
Для
представления
такой
простой
диаграммы
в
коде
сначала
нужно
создать
класс,
который
определяет
общие
свойства
всех
геометрических
фигур:
class
Shape
{ puЫic:
virtual virtual virtual virtual virtual 11 ...
};
Point |
center() |
const |
=О; |
11 |
|||
void |
move(Point |
to) |
=О; |
|
|
||
void |
draw () |
const = |
О; |
|
// |
||
void |
rotate(int |
angle) |
=О; |
||||
-Shape () |
{} |
|
|
|
|
/ / |
Чисто |
виртуальная |
|
Вывод |
на текущем |
"холсте" |
Деструктор |
|
Естественно, |
этот |
интерфейс |
является |
абстрактным |
классом; |
что |
касается
представления,
то
ничто
(кроме
местоположения
указателя
на
vtЫ)
не
яв
ляется
общим
для
каждого
Shape.
В
этом
определении
мы
можем
написать
только
общие
функции,
управляющие
векторами
указателей
на фигуры:
//
Поворот
элементов
v
на
угол
angle
градусов:
void {
rotate_all(vector<Shape*>&
v,
int
angle)
for
(auto р : v) p->rotate(angle);
Чтобы определить конкретную фигуру, мы должны указать, что это Shape, определить ее конкретные свойства (включая виртуальные функции):
и
class Circle |
: puЬlic Shape |
|
{ |
|
|
puЬlic: |
|
|
Circle(Point р, |
int rad); |
/ /
Конструктор
Point |
center() |
|
{ |
|
|
return |
х; |
const
override
void |
move(Point |
|
( |
|
|
х |
= |
to; |
to)
override
void void
draw() |
const |
rotate(int) |
override; override (}
//
Простой
алгоритм
4.5.
Иерархии
классов
91
private: |
|
|
Point |
х; |
|
int |
r; |
|
} ; |
|
|
// 11
Центр Радиус
Пока что пример Shape и Circle не дает ничего нового по сравнению
мером Container и Vector_container, но мы можем продолжить:
с
при
11 Используем |
|
окружность как |
базовый |
класс |
для |
лица: |
class Smiley |
: |
puЫic Circle |
|
|
|
|
{ |
|
|
|
|
puЫic: |
|
|
|
|
Smiley(Point |
р, |
int |
rad) |
|
-Smiley () |
|
|
|
|
{ |
|
|
|
|
delete mouth; |
|
|
||
for (auto |
р |
: |
eyes) |
|
delete |
р; |
|
|
Circle{p,r},
mouth{nullptr}
{ |
} |
void void void
move(Point |
to) |
|
draw() |
const |
|
rotate(int) |
|
override; override; override;
void
add_eye(Shape*
s)
eyes.push_back(s);
void set_mouth(Shape* virtual void wink(int
11 ... |
|
private: |
|
vector<Shape*> |
eyes; |
Shape* mouth; |
|
} ; |
|
s); i);
//Подмигивание |
i-м |
глазом |
// |
Обычно два |
глаза |
Функция-член
push_
back
()
класса
vector
копирует
свой
аргумент
в
vector
(здесь
-
eyes)
в
качестве
последнего
элемента,
увеличивая
размер
вектора
на единицу. |
|
|
Теперь мы |
можем определить Smiley:: draw |
|
вов члена draw () |
базового класса и членов: |
()
с
использованием
вызо
void
Smiley::draw()
const
{
Circle:: draw (); |
||
for |
(auto р : |
eyes) |
|
p->draw(); |
|
mouth->draw(); |
|
92
Глава
4.
Классы
Обратите
внимание
на
то,
как
Smiley
хранит
глаза
в
vector
стандартной
библиотеки
и
удаляет их
в
своем
деструкторе.
Деструктор
Shape
является
виртуальным,
а
деструктор
Smiley
перекрывает
его.
Виртуальный
деструк
тор
необходим
для
абстрактного
класса,
потому
что
объект
производного
класса
обычно
обрабатывается
через
интерфейс,
предоставляемый
его
аб
страктным
базовым
классом.
В
частности,
он
может
быть удален с
помощью
указателя
на базовый
класс,
и
в
этом
случае
механизм
вызова
виртуальной
функции
гарантирует,
что
будет
вызван
правильный
деструктор.
Этот
де
структор
затем
неявно
вызывает
деструкторы
его
базовых
классов
и
членов.
В
этом
упрощенном
примере
задача
программиста
-
поместить
глаза
и
рот надлежащим образом в круг, представляющий лицо. Мы можем добавлять элементы данных, операции или
и
то
и
другое,
по
скольку
мы
определяем
новый
класс
путем
наследования.
Это
дает
большую
гибкость
-
вместе
с
соответствующими
возможностями
для
путаницы
и пло
хого
дизайна.
4.5.1.
Преимущества
иерархий
Иерархия
классов
предоставляет
две
разновидности
преимуществ.
•
•
Наследование интерфейса. Объект производного класса можно исполь |
|||||
зовать |
везде, где |
требуется |
объект |
базового класса. То есть базовый |
|
класс |
действует в |
качестве |
интерфейса |
для производного класса. При |
|
мерами являются |
классы Container и |
Shape. Такие классы часто яв |
|||
ляются абстрактными. |
|
|
|
||
Наследование реализации. Базовый |
класс предоставляет функции или |
||||
данные, что упрощает реализацию |
производных классов. Примерами |
||||
являются использование классом Smiley конструктора Circle и функ |
|||||
ции-члена Circle:: draw ().Такие базовые классы часто имеют члены |
|||||
данных и конструкторы. |
|
|
|
Конкретные
классы
-
особенно
с
небольшими
представлениями
--
очень
похожи
на
встроенные
типы:
мы
определяем
их
как
локальные
переменные,
обращаемся
к
ним
с
помощью
имен,
копируем
их
и
т.д.
Классы
в
иерархии
классов
различаются
-
обычно
для
них
выделяется
память
с
использовани
ем
оператора
new,
и
доступ
к
ним
выполняется
через
указатели
или
ссылки.
Например,
рассмотрим
функцию,
которая
считывает
данные,
описывающие
фигуры,
из
входного
потока
и
создает
соответствующие
объекты
Shape:
enum
class
Kind |
{ |
circle,
triangle,
smiley
);
11 Чтение описаний фигур из |
входного |
Shape* read_shape(istream& |
is) |
потока
is:
{
|
|
|
|
|
|
|
|
4.5. Иерархии классов |
|
11 ... |
Чтение |
заголовка фигуры |
из |
is и поиск |
его |
Kind k ... |
|||
switch |
(k) |
|
|
|
|
|
|
|
|
case |
Kind::circle: |
|
|
|
|
|
|||
|
// |
Чтение |
данных |
окружности |
{Point,int} |
ври |
r |
||
|
return |
new |
Circle{p,rl; |
|
|
|
|
||
case |
Kind::triangle: |
|
|
|
|
|
|||
|
// |
Чтение |
вершин |
треугольника |
{Point,Point,Point} |
||||
|
/ / |
в pl, р2 и рЗ |
|
|
|
|
|
||
|
return |
new |
Triangle{pl,p2,p3); |
|
|
|
|||
case |
Kind::smiley: |
|
|
|
|
|
|||
|
// |
Чтение |
данных |
смайлика {Point,int,Shape,Shape,Shape} |
|||||
|
11 |
в р, |
r, |
el, е2 |
и т |
|
|
|
|
|
Smiley* |
ps |
= new |
Smiley{p,r); |
|
|
|
||
|
ps->add_eye(ell; |
|
|
|
|
|
|||
|
ps->add_eye(e2); |
|
|
|
|
|
|||
|
ps->set_mouth(ml; |
|
|
|
|
|
|||
|
return |
ps; |
|
|
|
|
|
|
93
Программа
может
использовать
функцию
чтения
фигуры
следующим
образом:
void {
user
()
std::vector<Shape*> |
v; |
|
while |
(cin) |
|
v.push_back(read_shape(cin)); |
draw_all (v); rotate_all(v,45);
/ / Вызов //Вызов
draw(} |
каждого элемента |
|
rotate(45) |
каждого элемента |
for(auto р delete
: |
v) |
р; |
|
11
Не
забудьте
удалить
элементы
Очевидно,
что
пример
упрощен
(особенно
в
отношении
обработки
оши
бок),
но он
наглядно
иллюстрирует,
что
user
()
не
имеет
абсолютно
никакого
представления
о
том,
с
какими
видами
фигур
он
работает.
Код
user
()
мо
жет
быть
скомпилирован
один
раз
и
позже
использоваться
для
новых
видов
фигур, добавленных
в
программу.
Обратите
внимание,
что
нет
никаких
ука
зателей
на
фигуры
вне
user
(),
поэтому
за
освобождение
объектов
отвеча
ет
сама
функция.
Это
делается
с
помощью
оператора
delete,
полагаясь
при
этом
на
виртуальный
деструктор
Shape.
Поскольку
деструктор
является
вир
туальным,
delete
вызывает
деструктор
для
наиболее
позднего
производного
класса.
Это
имеет решающее
значение,
поскольку
производный
класс
может
захватывать
всевозможные
ресурсы
(такие,
как
дескрипторы
файлов,
бло
кировки
и
выходные
потоки),
которые
должны
быть
освобождены.
В
нашем
94
Глава
4.
Классы
случае
Srniley
удаляет
свои
объекты eyes
и
rnouth.
Как
только
это
сделано,
вызывается
деструктор
Circle.
Объекты
строятся
конструкторами
"снизу
вверх"
(сначала
-
базовый),
а
уничтожаются
деструкторами
"сверху
вниз"
(сначала
-
производные).
4.5.2.
Навигация
по
иерархии
Функция
read_shape
()
возвращает
Shape*,
так что
мы
можем
рассма
тривать
все
фигуры
единообразно.
Однако
что
нам
делать,
если
мы
захотим
использовать
функцию-член,
имеющуюся
только
в
определенном
произ
водном
классе,
как,
например,
wink
()
в
классе
Srniley?
Мы
можем
запро
сить,
"является
ли
данная
фигура
Srniley?",
воспользовавшись
оператором
dynarnic
cast:
Shape* |
ps{read |
|
if |
(Smiley*p = |
_shape(cin) ); dynamic_cast<Smiley*>(ps))
//
ps
указывает
на
Smiley?
//Да,
это
Smiley;
используем
его
else
11
Нет,
это
не
Smiley,
делаем
что-то
иное
Если
во
время
выполнения
объект,
на
который
указывает
аргумент
dynarnic
_
cast
(в
данном
случае
-
ps)
не
имеет
ожидаемого
типа
(в
данном
случае
-
Srniley)
или
типа
класса,
производного
от
ожидаемого,
то
dynarnic
_ cast
возвращает
nullptr.
Мы |
используем |
|
приведение dynarnic_ cast к типу |
указателя, |
когда аргу |
|
мент |
представляет |
собой корректный указатель. Затем |
мы проверяем, явля |
|||
ется |
ли |
результат |
равным nullptr. Эту проверку часто удобно |
помещать в |
инициализацию
переменной
в
условии.
Мы можем использовать dynarnic_cast и для приведения типу. Если объект не относится к ожидаемому типу, dynarnic
к
ссылочному cast в этом
случае
генерирует
исключение
bad
_
cast:
Shape*
Smiley&
ps r
{read_shape(cin) ); {dynamic_cast<Smiley&>(*ps)
);
11 11
Может генерировать
исключение std: :bad
cast
Код
будет
более
чистым,
если
dynarnic
_
cast
используется
с
ограничения
ми.
Если
мы
сможем
избежать
использования
информации
о
типе,
то
сможем
написать
более
простой
и
эффективный
код.
Но
иногда
информация
о
типе
теряется
и
должна
быть
восстановлена.
Обычно
это
происходит,
когда
мы
пе
редаем
объект
какой-либо
системе,
которая
принимает
интерфейс,
определен
ный
базовым
классом.
Когда
эта
система
позже
возвращает
объект
обратно,
96
Глава
4.
Классы
В
качестве
приятного
побочного
эффекта
этого
изменения
нам
больше
не
нужно
определять деструктор
для
Smiley.
Компилятор
будет
неявно генери
ровать
деструктор,
который
выполнит
требуемое
уничтожение
unique
_ptr
(§5.3)
в
векторе.
Код,
использующий
unique
_ptr,
будет
таким
же
эффектив
ным,
как
и
код
с
обычными
указателями.
Теперь
рассмотрим
пользователей
read
_
shape
()
:
// |
Читаем описания фигур из входного |
потока is: |
|
unique_ptr<Shape> read_shape(istream& |
is) |
|
|
{ |
|
|
|
|
// Читаем заголовок фигуры из is |
и находим |
ее |
|
switch (k) |
|
|
|
{ |
|
|
Kind |
k |
case |
Kind::circle: |
|
|
|
|
// Читаем данные |
Circle {Point,int} |
ври |
r |
|
return unique_ptr<Shape>{new Circle{p,r)); |
|
||
|
11 ... |
|
|
|
//
§13.2.1
void {
user
()
vector<unique_ptr<Shape>> |
v; |
|
|
while (cin) |
|
|
|
v.push_back(read_shape(cin)); |
|
||
draw_all (v); |
// Вызов |
draw() |
для |
rotate_all(v,45); |
//Вызов |
rotate(45) |
ка'11;l!ого элемента для ка'11;l!ого элемента
11
Все
объекты
Shape
неявно
уничтожаются
Теперь
каждым
объектом
владеет
unique
_ptr,
который
удаляет
свой
объект,
когда
он
больше
не
нужен,
т.е.
когда
его
unique
_ptr
выходит
из
области
ви
димости. |
|
|
Чтобы версия user |
() с использованием unique_ptr была работоспособ |
|
на, нам |
нужны версии |
функций draw_all () и rotate_all (),которые при |
нимали |
бы в качест.ве |
аргументов vector<unique_ptr<Shape>>. Написа |
ние множества таких_all ()-функций может оказаться очень утомительным |
||
занятием; в §6.3.2 показано альтернативное решение. |
4.6.
Советы
[1] [2]
[3]
Выражайте идеи |
непосредственно в коде; §4.1; [CG:P. I]. |
||
Конкретный тип |
является |
простейшей разновидностью класса. Где это |
|
возможно, предпочитайте |
конкретные типы более сложным классам и |
||
простым структурам данных; §4.2; [CG:C. l О]. |
|||
Для |
представления простых концепций используйте конкретные клас |
||
сы; |
§4.2. |
|
|
4.6.
Советы
97
[4]
[5]
[6]
[7]
[8]
[9]
[1О]
[ 11] [12)
[13)
[14)
[15)
[16)
[17]
[18)
[19)
[20)
[21)
Для компонентов, критичных с |
точки зрения производительности, пред |
||||||||||||
почитайте конкретные |
классы иерархиям классов; §4.2. |
|
|
||||||||||
Для управления |
инициализацией |
объектов |
определяйте конструкторы; |
||||||||||
§4.2.1, §5.1.1; [CG:C.40) [CG:C.41 |
]. |
|
|
|
|
|
|
|
|
||||
Делайте функцию членом только |
тогда, |
когда необходим |
непосред |
||||||||||
ственный доступ к представлению класса; §4.2.1; [CG:C.4]. |
|
|
|||||||||||
Определяйте |
операторы, в первую |
очередь, |
для |
имитации |
обычного |
||||||||
применения; |
§4.2.1; [CG:C.160). |
|
|
|
|
|
|
|
|
|
|||
Используйте свободные функции |
для симметричных операторов; |
§4.2.1; |
|||||||||||
[CG:C.161 ]. |
|
|
|
|
|
|
|
|
|
|
|
|
|
Объявляйте функции-члены, которые |
не |
модифицируют объект, как |
|||||||||||
cons t; §4.2.1. |
|
|
|
|
|
|
|
|
|
|
|
|
|
Если конструктор захватывает |
ресурс, |
необходим |
деструктор, освобо |
||||||||||
|
|
2 |
; §4.2.2; [CG:C.20). |
|
|
|
|
|
|||||
ждающий этот ресурс |
|
|
|
|
|
||||||||
Избегайте операций с "голыми" new и delete; §4.2.2; [CG:R.11 ]. |
|
||||||||||||
Для управления |
ресурсами используйте дескрипторы ресурсов и |
идио |
|||||||||||
му RAII; §4.2.2; [CG:R. l ]. |
|
|
|
|
|
|
|
|
|
|
|||
Если класс представляет собой |
контейнер, |
снабдите его конструктором |
|||||||||||
на основе списка инициализации; §4.2.3; [CG:C. l 03). |
|
|
|||||||||||
Когда требуется |
полное разделение |
интерфейса и |
реализации, исполь |
||||||||||
зуйте в качестве |
интерфейсов абстрактные |
классы; |
§4.3; [CG:C.122). |
||||||||||
Обращайтесь |
к |
полиморфным |
объектам с |
помощью указателей |
и ссы |
||||||||
лок; §4.3. |
|
|
|
|
|
|
|
|
|
|
|
|
|
Абстрактный |
класс |
обычно |
не |
|
нуждается |
в |
конструкторе; |
§4.3; |
|||||
[CG:C.126). |
|
|
|
|
|
|
|
|
|
|
|
|
|
Используйте |
иерархии классов |
для |
представления |
концепций с |
иерар |
||||||||
хической структурой; §4.5. |
|
|
|
|
|
|
|
|
|
|
|||
Класс с виртуальной функцией |
должен иметь |
виртуальный деструктор; |
|||||||||||
§4.5; [CG:C.127). |
|
|
|
|
|
|
|
|
|
|
|
||
Используйте |
ключевое слово overr ide для явного указания |
перекры |
|||||||||||
тия в больших иерархиях классов; |
§4.5.1; [CG:C.128). |
|
|
||||||||||
При проектировании иерархии |
классов различайте |
наследование |
реали |
||||||||||
зации и наследование |
интерфейса; |
§4.5.1; [CG:C.129). |
|
|
|||||||||
Используйте |
dynamic_cast там, |
где |
навигация по иерархии классов |
||||||||||
неизбежна; §4.5.2; [CG:C.146). |
|
|
|
|
|
|
|
|
|
|
2 |
Так как ресурс может захватываться не только в конструкторе, деструктор |
|
|
|
|
бождать все захваченные за время жизни объекта ресурсы. - |
Примеч. ред. |
должен
осво
98
Глава
4.
Классы
[22]
[23]
[24]
Используйте |
dynamic_ cast |
для |
ссылочного |
типа там, |
где |
невозмож |
ность найти [CG:C.147].
необходимый
класс рассматривается
как
ошибка;
§4.5.2;
Используйте |
dynamic_ cast для типа указателя там, где невозмож |
ность найти |
необходимый класс рассматривается как корректный вари |
ант; §4.5.2; [CG:C.148]. |
|
Используйте unique_ptr или shared_ptr, чтобы не забывать удалять |
|
объекты, созданные с помощью оператора new; §4.5.3; [CG:C.149]. |
5
Основные
операции
•
•
• •
•
|
Когда кто-то |
говорит "Я хочу язык программирования, |
|
|
в котором достаточно просто сказать, |
|
|
что я хочу сделать", дайте емуледенец. |
|
|
-Алан Перлис |
Введение |
|
|
Основные операции |
|
|
Преобразования типов |
|
|
Инициализаторы членов |
|
|
Копирование и перемещение |
|
|
Копирование контейнеров |
|
|
Перемещение контейнеров |
|
|
Управление |
ресурсами |
|
Обычные операции |
|
|
Сравнения |
|
|
Операции |
с контейнерами |
|
Операции |
ввода-вывода |
|
Пользовательские литералы |
|
|
swap() |
|
|
hash<> |
|
|
Советы |
|
|
5.1.
Введение
Некоторые
операции,
такие
как
инициализация, |
присваивание, |
копирова
ние
и
перемещение,
являются
фундаментальными
в
том
смысле,
что
правила
языка
делают
о
них
определенные
предположения.
Другие
операции,
такие
как==
и<<,
имеют
обычный
стандартный
смысл,
который
опасно
игнори
ровать.