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) написать следующий код:
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); // ошибка на этапе выполнения:
// Year::Invalid
Стоило ли выполнять дополнительную работу и вводить обозначения для лет? Естественно, это зависит от того, какие задачи вы собираетесь решать с помощью типа Date, но в данном случае мы сомневаемся в этом и не хотели бы создавать отдельный класс
Year
.
Когда мы программируем, то всегда устанавливаем критерии качества для данного приложения. Как правило, мы не можем позволить себе роскошь очень долго искать идеальное решение, если уже нашли достаточно хорошее. Втягиваясь в поиски наилучшего решения, мы настолько запутаем программу, что она станет хуже, чем первоначальный вариант. Как сказал Вольтер: “Лучшее — враг хорошего”.
Обратите внимание на слова
static const
в определениях переменных
min
и
max
. Они позволяют нам определить символические константы для целых типов в классах. Использование модификатора
static
по отношению к члену класса гарантирует, что в программе существует только одна копия его значения, а не по одной копии на каждый объект данного класса.
9.7.2. Копирование
Мы всегда должны создавать объекты, иначе говоря, всегда предусматривать инициализацию и конструкторы. Вероятно, это самые важные члены класса: для того чтобы написать их, необходимо решить, как инициализировать объект и что значит корректность его значений (т.е. определить инвариант). Уже даже размышления об инициализации помогут вам избежать ошибок.
Затем необходимо решить, можно ли копировать объекты и как это делать? Для класса
Date
или перечисления
Month
ответ очевиден: копирование необходимо, и его смысл тривиален: просто копируются все члены класса. Фактически это предусмотрено по умолчанию. Если не указано ничего другого, компьютер сделает именно это. Например, если перечисление
Date
используется для инициализации или стоит в правой части оператора присваивания, то все его члены будут скопированы.