Программирование. Принципы и практика использования C++ Исправленное издание, стр. 131
Малопонятные операторы и необычное использование операторов могут запутать программу и стать источником ошибок. Более на эту тему мы распространяться не будем. Просто в следующих главах применим перегрузку операторов в соответствующих местах.
Интересно, что чаще всего для перегрузки выбирают не операторы
+–*/===!=<[]()9.7. Интерфейсы классов
struct• Интерфейс должен быть полным.
• Интерфейс должен быть минимальным.
• Класс должен иметь конструкторы.
• Класс доложен поддерживать копирование (или явно запрещать его) (см. раздел 14.2.4).
• Следует предусмотреть тщательную проверку типов аргументов.
• Необходимо идентифицировать немодифицирующие функции-члены (см. раздел 9.7.4).
• Деструктор должен освобождать все ресурсы (см. раздел 17.5). См. также раздел 5.5, в котором описано, как выявлять ошибки и сообщать о них на этапе выполнения программы.
Первые два принципа можно подытожить так: “Интерфейс должен быть как можно более маленьким, но не меньше необходимого”. Интерфейс должен быть маленьким, потому что его легче изучить и запомнить, а программист, занимающийся реализацией класса, не будет терять время на реализацию излишних или редко используемых функций. Кроме того, небольшой интерфейс означает, что если что-то пойдет не так, как задумано, для поиска причины потребуется проверить лишь несколько функций. В среднем чем больше открытых функций, тем труднее найти ошибку, — пожалуйста, не усложняйте себе жизнь, создавая классы с открытыми данными. Но, разумеется, интерфейс должен быть полным, в противном случае он будет бесполезным. Нам не нужен интерфейс, который не позволяет нам делать то, что действительно необходимо.
Перейдем к изучению менее абстрактных и более реалистичных понятий, поддерживаемых в языке С++.
9.7.1. Типы аргументов
Определяя конструктор класса
DateintDate d1(4,5,2005); // Ой! Год 4, день 2005Date 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; // день};Когда мы используем тип
MonthMonthDate dx1(1998, 4, 3); // ошибка: 2-й аргумент не имеет // тип MonthDate dx2(1998, 4, Date::mar); // ошибка: 2-й аргумент не имеет // тип MonthDate dx2(4, Date::mar, 1998); // Ой: ошибка на этапе выполнения: // день 1998Date dx2(Date::mar, 4, 1998); // ошибка: 2-й аргумент не имеет // тип MonthDate dx3(1998, Date::mar, 30); // OKЭтот код решает много проблем. Обратите внимание на квалификатор
Datemar: Date::marmarDateDate.marDatemar::.
А нельзя ли подобным образом выявить путаницу между днем месяца и годом? Можно, но решение этой проблемы будет не таким элегантным, как для типа
Month;Вероятно, было бы лучше всего (не вникая в предназначение класса Date) написать следующий код: