Литмир - Электронная Библиотека
A
A

Малопонятные операторы и необычное использование операторов могут запутать программу и стать источником ошибок. Более на эту тему мы распространяться не будем. Просто в следующих главах применим перегрузку операторов в соответствующих местах.

Интересно, что чаще всего для перегрузки выбирают не операторы

+
,
,
*
, и
/
, как можно было бы предположить, а
=
,
==
,
!=
,
<
,
[]
и
()
.

9.7. Интерфейсы классов

 

Программирование. Принципы и практика использования C++ Исправленное издание - _002.png
 Ранее мы уже указывали, что открытый интерфейс и реализация класса должны быть отделены друг от друга. Поскольку в языке С++ остается возможность использовать простые структуры
struct
, некоторые профессионалы могут не согласиться с этим утверждением. Однако как разработать хороший интерфейс? Чем хороший интерфейс отличается от плохого? Частично на эти вопросы можно ответить только с помощью примеров, но существует несколько общих принципов, которые поддерживаются в языке С++.

• Интерфейс должен быть полным.

• Интерфейс должен быть минимальным.

• Класс должен иметь конструкторы.

• Класс доложен поддерживать копирование (или явно запрещать его) (см. раздел 14.2.4).

• Следует предусмотреть тщательную проверку типов аргументов.

• Необходимо идентифицировать немодифицирующие функции-члены (см. раздел 9.7.4).

• Деструктор должен освобождать все ресурсы (см. раздел 17.5). См. также раздел 5.5, в котором описано, как выявлять ошибки и сообщать о них на этапе выполнения программы.

Первые два принципа можно подытожить так: “Интерфейс должен быть как можно более маленьким, но не меньше необходимого”. Интерфейс должен быть маленьким, потому что его легче изучить и запомнить, а программист, занимающийся реализацией класса, не будет терять время на реализацию излишних или редко используемых функций. Кроме того, небольшой интерфейс означает, что если что-то пойдет не так, как задумано, для поиска причины потребуется проверить лишь несколько функций. В среднем чем больше открытых функций, тем труднее найти ошибку, — пожалуйста, не усложняйте себе жизнь, создавая классы с открытыми данными. Но, разумеется, интерфейс должен быть полным, в противном случае он будет бесполезным. Нам не нужен интерфейс, который не позволяет нам делать то, что действительно необходимо.

Перейдем к изучению менее абстрактных и более реалистичных понятий, поддерживаемых в языке С++. 

9.7.1. Типы аргументов

Определяя конструктор класса

Date
в разделе 9.4.3, мы использовали в качестве аргументов три переменные типа
int
. Это породило несколько проблем.

Date d1(4,5,2005); // Ой! Год 4, день 2005

Date d2(2005,4,5); // 5 апреля или 4 мая?

Первая проблема (недопустимый день месяца) легко решается путем проверки в конструкторе. Однако вторую проблему (путаницу между месяцем и днем месяца) невозможно выявить с помощью кода, написанного пользователем. Она возникает из-за того, что существуют разные соглашения о записи дат; например, 4/5 в США означает 5 апреля, а в Англии — 4 мая. Поскольку эту проблему невозможно устранить с помощью вычислений, мы должны придумать что-то еще. Очевидно, следует использовать систему типов.

// простой класс Date (использует тип Month)

class Date {

public:

  enum Month {

    jan=1, feb, mar, apr, may, jun, jul, aug, sep, oct, nov, dec

  };

  Date(int y, Month m, int d); // проверка даты и инициализация

  // ...

private:

  int y; // год

  Month m;

  int d; // день

};

Когда мы используем тип

Month
, компилятор выдаст ошибку, если мы поменяем местами месяц и день. Кроме того, перечисление
Month
позволяет использовать символические имена. Такие имена, как правило, легче читать и записывать, чем работать с числами, подвергаясь риску ошибиться.

Date dx1(1998, 4, 3);          // ошибка: 2-й аргумент не имеет

                               // тип Month

Date dx2(1998, 4, Date::mar);  // ошибка: 2-й аргумент не имеет

                               // тип Month

Date dx2(4, Date::mar, 1998);  // Ой: ошибка на этапе выполнения:

                               // день 1998

Date dx2(Date::mar, 4, 1998);  // ошибка: 2-й аргумент не имеет

                               // тип Month

Date dx3(1998, Date::mar, 30); // OK

Этот код решает много проблем. Обратите внимание на квалификатор

Date
перечисления
mar: Date::mar
. Тем самым мы указываем, что это перечисление
mar
из класса
Date
. Это не эквивалентно обозначению
Date.mar
, поскольку
Date
— это не объект, а тип, а
mar
— не член класса, а символическая константа из перечисления, объявленного в классе. Обозначение
::
используется после имени класса (или пространства имен; см. раздел 8.7), а
.
(точка) — после имени объекта.

 

Программирование. Принципы и практика использования C++ Исправленное издание - _001.png
 Когда есть выбор, ошибки следует выявлять на этапе компиляции, а не на этапе выполнения программы. Мы предпочитаем, чтобы ошибки вылавливал компилятор, а не искать, в каком месте кода возникла ошибка. Кроме того, для выявления ошибок на этапе компиляции не требуется писать и выполнять специальный код для проверки.

А нельзя ли подобным образом выявить путаницу между днем месяца и годом? Можно, но решение этой проблемы будет не таким элегантным, как для типа

Month;
помимо всего прочего, возможно, что мы имели в виду именно четвертый год. Даже если мы ограничимся современной эпохой, в перечисление придется включать слишком много лет.

Вероятно, было бы лучше всего (не вникая в предназначение класса Date) написать следующий код:

131
{"b":"847443","o":1}