Добавил:
t.me Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:

2 семестр / Литература / Язык программирования С++. Краткий курс. Страуструп

.pdf
Скачиваний:
9
Добавлен:
16.07.2023
Размер:
31.34 Mб
Скачать

70

Глава

3.

Модульность

i-й

элемент

Vector

существует

независимо

от

вызова

оператора

индекса,

по­

этому мы можем вернуть ссылку на него.

С другой стороны, по завершении функции ее локальные переменные

чезают, поэтому мы не должны возвращать указатель или ссылку на них:

ис­

int&

bad ()

{

 

 

 

int

х;

 

11

".

 

return

х;

//

Плохо:

возврат

ссылки

на

локальную

переменную

х

К

счастью,

все

основные

компиляторы

С++

в

состоянии

обнаружить

эту

оче­

видную ошибку в bad ( ) . Возврат ссылки или значения

"малого"

типа

эффективно,

но

как

же

пере­

давать

из

функции

большие

количества

информации?

Рассмотрим

следую­

щий

код:

Matrix operator+(const Matrix& х,

const Matrix& у)

{

 

 

 

 

 

 

Matrix

res;

 

 

 

 

 

11 ...

Для

всех

res[i,j]

имеем res[i,j]

x[i,j]+y[i,j]

return

res;

 

 

 

 

 

...

Matrix

11 ...

Matrix

ml,

=

m2; ml+m2;

//

Без

копирования

Матрица

Matrix

может

быть

очень

большой,

с

дорогостоящим

копирова­

нием

даже

на

современном

аппаратном

обеспечении.

Поэтому,

чтобы

избе­

жать

копирования,

мы

создаем

перемещающий

конструктор

Matrix

(§5.2.2),

а

операция

перемещения

Matrix

из

оператора

operator+

()

оказывается

очень

дешевой.

Нам

не

требуется

возвращаться

к

ручному

управлению

памя­

тью:

Matrix*

add(const

Matrix&

х,

const

Matrix&

у)

// Сложный и

чреватый

 

11

ошибками стиль

ХХ века

Matrix*

р

=

new

11 ...

Для

всех

return

р;

 

 

Matrix;

*p[i,j],

*p[i,j]

=

x[i,j]+y[i,j]

...

Matrix

11 ...

Matrix*

11 ...

delete

ml,

mЗ;

m2; = add(ml,m2);

11

Копируем только указатель

11

Легко за быть удалить . ..

72

Глава

3.

Модульность

Конструкция

auto

[n,

v]

объявляет

две

локальные

переменные n

и

v

с

ти­

пами,

выведенными

из

возвращаемого

типа

read

_

entry

().

Этот

механизм

придания

локальных

имен

членам

объекта

класса

называется структурным

связыванием

(structured

Ьinding).

Рассмотрим

еще

один

пример:

map<string,int>

m;

 

 

 

11

... Заполнение

т ...

 

 

for

(const

auto

[key,value]

: m)

cout << "1"

<<

key

"," <<

value

<<

"}\n";

Как

обычно,

можно

декорировать

auto

с

помощью

const

и&.

Например:

void

incr(map<string,int>&m)

1

for

(auto&

[key,value]

 

 

 

++value;

// m)

Увеличение

значения

кажцого

элемента

т

Когда

структурное

связывание

используется

для

класса

без

закрытых

дан­

ных,

легко

увидеть,

как

выполняется

такое

связывание:

для

связывания

долж­

но

быть

определено

столько

же

имен,

сколько

имеется

нестатических

чле­

нов-данных

класса,

и

каждое

введенное

имя

связывается

с

соответствующим

членом.

В

качестве

объектного

кода

по

сравнению

с

явным

использованием

составного

объекта

нет

никакого

отличия;

использование

структурного

свя­

зывания -

просто иное выражение все той же концепции.

 

Можно также работать и с классами, в которых доступ

осуществляется

рез функции-члены. Например:

 

че­

complex<douЫe>

z =

auto [re,im] =

z+2;

{1,2}; // rе=З;

im=2

ит

Объект типа complex имеет два члена-данных, но его интерфейс состо­ из функций доступа, таких как real () и imag ().Отображение complex

<douЫe>

на

две

локальные

переменные,

такие

как

re

и

im,

осуществимо

и

эффективно,

но

техника,

позволяющая

это

сделать,

выходит

за

рамки дан­

ной

книги.

3.7.

Советы

(1]

[2]

Различайте объявления (используемые в качестве интерфейсов) и опре­

деления (используемые как

реализации); §3.1.

 

Используйте заголовочные

файлы для представления интерфейсов

и

для подчеркивания логической структуры; §3.2; [CG:SF.3).

 

[3]

[4]

[5]

[6]

[7]

[8] [9]

[1 О]

[11)

[12)

[13)

[14)

[15)

[16)

[17] [ 18)

[19)

[20)

[21]

 

 

 

 

 

 

 

 

 

 

 

 

 

3.7. Советы

 

73

Включайте заголовочные файлы с

помощью директив

#include

в ис­

ходные

файлы, которые реализуют их функции; §3.2; [CG:SF.5].

 

 

Избегайте в заголовочных файлах

определений функций, не

являющих­

ся inline; §3.2; [CG:SF.2].

 

 

 

 

 

 

 

 

 

 

 

 

 

 

Предпочитайте модули заголовочным файлам (где имеется

поддержка

module); §3.3.

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

Используйте

пространства

имен для выражения

логической

структуры;

§3.4; [CG:SF.20).

 

 

 

 

 

 

 

 

 

 

 

 

 

 

Используйте

директивы using

при переделке

приложений, для базовых

библиотек (таких, как std) или в

локальной

области

видимости;

§3.4;

[CG:SF.6] [CG:SF.7].

 

 

 

 

 

 

 

 

 

 

 

 

 

 

Не помещайте

директивы using в

заголовочный файл;

§3.4; [CG:SF.7].

Генерируйте

исключения для указания того, что

вы не

в состоянии вы­

полнить определенную задачу;

§3.5; [CG:E.2].

 

 

 

 

 

 

 

 

Используйте

 

исключения

только

для

обработки

ошибок;

§3.5.3;

[CG:E.3].

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

Применяйте

коды ошибок

там, где

предполагается, что

обрабатывать

ошибки будет

непосредственно

вызывающая функция;

§3.5.3.

 

 

Генерируйте

исключения, если

предполагается, что ошибка

должна пе­

редаваться через несколько

вызовов

функций;

§3.5.3.

 

 

 

 

 

Если

вы не знаете, что лучше использовать, исключения или коды

оши­

бок,

предпочитайте исключения; §3.5.3.

 

 

 

 

 

 

 

 

 

Разрабатывайте стратегию обработки ошибок как можно раньше при

проектировании; §3.5; [CG:E.12).

 

 

 

 

 

 

 

 

 

 

 

 

Используйте

для исключений специально

разработанные

пользователь­

 

 

 

 

ские

типы (не

встроенные типы);

§3.5.1.

 

 

 

 

 

 

 

 

 

Не пытайтесь

перехватывать все

исключения в

каждой функции;

§3.5;

[CG:E.7].

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

Предпочитайте идиому RAll явным

trу-блокам;

§3.5.1, §3.5.2; [CG:E.6).

Если

ваша функция не может

генерировать

исключения, объявите ее

как noexcept; §3.5; [CG:E.12).

 

 

 

 

 

 

 

 

 

 

 

 

 

Конструктор

должен обеспечивать выполнение инварианта

и генериро­

вать

исключение, если не в

состоянии это

сделать;

§3.5.2; [CG:E.5].

Проектируйте

свою стратегию обработки

ошибок

вокруг

инвариантов;

§3.5.2; [CG:E.4].

 

 

 

 

 

 

 

 

 

 

 

 

 

 

То, что

можно проверить во время

компиляции, обычно

лучше

прове­

рять

во

время

компиляции;

§3.5.5 [CG:P.4] [CG:P.5].

 

 

 

 

 

74

Глава

3.

Модульность

(22]

(23]

(24]

(25] (26]

Передавайте "малые" значения

по ссылке; §3.6.1; [CG:F.16].

в

функции

по

значению,

а

"большие"

-

Предпочитайте

передачу по константной

неконстантной

ссылке; (CG:F.17].

ссылке

передаче

по

обычной,

Возвращайте

информацию из

ей значений (а не с

помощью

[CG:F.21].

 

 

функций выходных

в

виде возвращаемых функци­

параметров); §3.6.2; [CG:F.20]

Не

злоупотребляйте

выводом типа возвращаемого значения; §3.6.2.

Не

злоупотребляйте

структурным связыванием; именованные возвра­

щаемые типы зачастую являются более ясной документацией; §3.6.3.

4 Классы

 

-

+

Введение

+ Конкретные типы

 

Арифметический тип

 

Контейнер

+

Инициализация контейнеров

Абстрактные типы

+

Виртуальные функции

+

Иерархии классов

 

Преимущества иерархий

 

Навигация по иерархии

 

Избежание утечки ресурсов

Советы

Эти типы не абстрактны -

они реальны

так же, как и типы int и douЫe.

 

Дуг МакИлрой

4.1.

Введение

Цель

этой

и

следующих

трех

глав

-

дать

вам

представление

о

поддержке

абстракции

и

управления

ресурсами

С++

без

погружения

в

детали.

В этой главе неформально представлены способы определения и ис­

пользования новых типов (пользовательских типов). В частности, здесь

представлены основные

свойства, методы реализации и языковые сред­

ства, используемые для

конкретных классов, абстрактных классов и

иерархий

классов.

 

 

 

В главе 5,

"Основные операции", представлены операции,

которые име­

ют определенный смысл в С++, такие как конструкторы,

деструкторы

и присваивания. В ней излагаются правила их совместного использова­

ния для управления жизненным циклом

объектов и поддержки просто­

го, эффективного и полного управления

ресурсами.

 

76

Глава

4.

Классы

В главе 6, "Шаблоны'', представлены шаблоны как механизм для пара­

метризации типов и алгоритмов (другими) типами и алгоритмами. Вы­

числения над пользовательскими и встроенными типами представлены

в виде функций, иногда обобщенных

до шаблонных функций и функцио­

ншzьных объектов.

 

 

В главе 7, "Концепты и обобщенное

программирование", представлен

обзор концепций, технологий и языковых возможностей, лежащих в ос­

нове обобщенного программирования. Основное внимание

уделяется

определению и использованию концептов для точных определений ин­

терфейсов шаблонов и проектирования. Вводятся шаблоны

с перемен­

ныJи

количеством параметров (вариативные шаблоны) для

определе­

ния

наиболее общих и наиболее гибких интерфейсов.

 

Это

языковые средства, поддерживающие

стили программирования, извест­

ные

как объектно-ориентированное программирование и обобщенное про­

граммирование. В главах 8-15 приводятся

примеры возможностей стандарт­

ной

библиотеки и их использования.

 

Центральной

особенностью

языка

С++

является

класс.

Класс

-

это

поль­

зовательский

тип,

представляющий

в

коде

программы

некоторую

концепцию.

Всякий

раз,

когда

наш

дизайн

программы

содержит

полезную

концепцию,

идею,

сущность

и

тому

подобное,

мы

стараемся

представить

ее

в

программе

в

виде

класса,

чтобы

идея

содержалась

в

коде,

а

не

только

в

наших

головах,

проектной

документации

или

в

некоторых

комментариях.

Программу,

по­

строенную

из

хорошо

подобранного

набора

классов,

намного

легче

понять

и

сделать

безошибочной,

чем

программу,

которая

строится

непосредственно

на

фундаменте

фундаментальных

встроенных

типов.

В

частности,

библиотеки

часто

предлагают

именно

наборы

классов.

По

сути,

все

языковые

средства

(помимо

основных

типов,

операторов

и

инструкций)

существуют

для

того,

чтобы

помочь

определить

лучшие

классы

или

более

удобно

их

использовать.

Под

"лучшими"

классами

я

имею

в

виду

более

правильные,

более легкие

в

обслуживании,

более эффективные, более

элегантные,

более

простые

в

применении,

более удобочитаемые

и

надежные.

Большинство

методов

программирования

опирается

на

разработку

и

реализа­

цию

конкретных

видов

классов.

Потребности

и

вкусы

программистов

очень

разнообразны, а смотрим только

потому базовую

и поддержка классов очень обширна. Здесь мы рас­ поддержку трех важных разновидностей классов:

• • •

конкретные классы (§4.2);

абстрактные классы (§4.3);

классы в иерархиях классов (§4.5).

4.2.

Конкретные

типы

77

Поразительное

количество

полезных

классов

оказываются

принадлежащими

одной

из

этих

трех

разновидностей.

Еще

больше

классов

можно

рассматри­

вать как простые варианты этих разновидностей или зованием сочетаний используемых для них методов.

реализуемые

с

исполь­

4.2.

Конкретные

типы

Основная

идея

конкретных

классов

заключается

в

том,

что

они

ведут

себя,

"как

встроенные

типы".

Например,

тип

комплексного

числа

или

целое

число

с

бесконечной

точностью

очень

похожи

на

встроенный

int,

за

исключением

того,

что

они

обладают

собственной

семантикой

и

собственными

наборами

операций.

Аналогично

vector

и

string

очень

похожи

на

встроенные

масси­

вы,

но

ведут

себя

куда

лучше

(§9.2,

§10.3,

§

11

.2).

Определяющей характеристикой конкретного представление является частью его определения.

типа является то, Во многих важных

что его случаях,

таких

как

vector,

это

представление

является

лишь

одним

или

нескольки­

ми

указателями

на

данные,

хранящиеся

в

другом

месте,

но

это

представление

присутствует

в

каждом

объекте

конкретного

класса.

Это

позволяет

обеспе­

чить

максимальную

эффективность

во

времени

и

пространстве.

В

частности,

это

позволяет:

помещать объекты конкретных типов в стек,

в статически выделенную

память и в другие объекты(§1.5);

 

 

 

 

обращаться к объектам непосредственно

(а не только с помощью указа­

телей или ссылок);

 

 

 

 

непосредственно и полностью инициализировать

объекты

(например,

с использованием конструкторов; §2.3);

 

 

 

 

копировать и перемещать объекты (§5.2).

 

 

 

 

Представление

может

быть

закрытым

(как

для

Vector,

§ 2.3)

и

доступным

только

через

функции-члены,

но

оно

присутствует.

Поэтому,

если

представ­

ление

изменяется

каким-либо

существенным

образом,

пользователь

должен

перекомпилировать

исходные

тексты. Это

цена

того,

что

конкретные

типы

ве­

дут

себя

точно

так

же,

как

встроенные

типы.

Для

типов,

которые

меняются

нечасто

и

в

которых

локальные

переменные

обеспечивают

столь

необходи­

мую

ясность

и

эффективность,

это

вполне

приемлемо

и

часто

просто

идеаль­

но.

Чтобы

повысить

гибкость,

конкретный

тип

может

хранить

основные

ча­

сти

своего

представления

в

свободной

памяти

(динамической

памяти,

куче)

и

получать

доступ

к

ним

через

часть,

хранящуюся

в

самом

объекте

класса.

Так

реализованы

vector и string; их можно

с тщательно

отработанными интерфейсами.

считать

дескрипторами

ресурсов