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), а .
(точка) — после имени объекта.
А нельзя ли подобным образом выявить путаницу между днем месяца и годом? Можно, но решение этой проблемы будет не таким элегантным, как для типа Month;
Вероятно, было бы лучше всего (не вникая в предназначение класса Date) написать следующий код:
class Year { // год в диапазоне [min:max)
static const int min = 1800;
static const int max = 2200;
public:
class Invalid { };
Year(int x) : y(x) { if (x
int year { return y; }
private:
int y;
};
class Date {
public:
enum Month {
jan=1, feb, mar, apr, may, jun, jul, aug, sep, oct, nov, dec
};
Date(Year y, Month m, int d); // проверка даты и инициализация
// ...
private:
Year y;
Month m;
int d; // день
};
Теперь получаем фрагмент кода.
Date dx1(Year(1998),4,3); // ошибка: 2-й аргумент — не Month
Date dx2(Year(1998),4,Date::mar); // ошибка: 2-й аргумент — не Month
Date dx2(4, Date::mar,Year(1998)); // ошибка: 1-й аргумент — не Year
Date dx2(Date::mar,4,Year(1998)); // ошибка: 2-й аргумент — не Month
Date dx3(Year(1998),Date::mar,30); // OK
Следующая фатальная и неожиданная ошибка выявится только на этапе выполнения программы.
Date dx2(Year(4),Date::mar,1998); // ошибка на этапе выполнения: