Читаем Программирование. Принципы и практика использования C++ Исправленное издание полностью

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); // ошибка на этапе выполнения:

Перейти на страницу:
Нет соединения с сервером, попробуйте зайти чуть позже