Читаем Программирование полностью

Здесь подразумевается, что переменная d будет изменяться, а переменная start_of_term — нет; другими словами, функция some_function() не может изменить переменную start_of_term. Откуда компилятору это известно? Дело в том, что мы сообщили ему об этом, объявив переменную start_of_term константой (const). Однако почему же с помощью функции day() можно прочитать переменную day из объекта start_of_term? В соответствии с предыдущим определением класса Date функция start_of_term.day() считается ошибкой, поскольку компилятор не знает, что функция day() не изменяет свой объект класса Date. Об этом в программе нигде не сказано, поэтому компилятор предполагает, что функция day() может модифицировать свой объект класса Date, и выдаст сообщение об ошибке.

  Решить эту проблему можно, разделив операции над классом, на модифицирующие и немодифицирующие. Это не только помогает понять суть класса, но и имеет очень важное практическое значение: операции, которые не модифицируют объект, можно применять к константным объектам. Рассмотрим пример.

class Date {

public:

  // ...

  int day() const;       // константный член: не может изменять

                         // объект

  Month month() const;   // константный член: не может изменять

                         // объект 

 int year() const;       // константный член: не может изменять

                         // объект

  void add_day(int n);   // неконстантный член: может изменять

                         // объект

  void add_month(int n); // неконстантный член: может изменять

                         // объект

  void add_year(int n);  // неконстантный член: может изменять

                         // объект

private:

  int y; // год

  Month m;

  int d; // день месяца

};

Date d(2000, Date::jan, 20);

const Date cd(2001, Date::feb, 21);

cout << d.day() << " — " << cd.day() << endl; // OK

d.add_day(1);  // OK

cd.add_day(1); // ошибка: cd — константа

Ключевое слово const в объявлении функции-члена стоит сразу после списка аргументов, чтобы обозначить, что эту функцию-член можно вызывать для константных объектов. Как только мы объявили функцию-член константной, компилятор берет с нас обещание не модифицировать объект. Рассмотрим пример.

int Date::day() const

{

  ++d; // ошибка: попытка изменить объект в константной

       // функции - члене

  return d;

}

Естественно, как правило, мы не собираемся мошенничать. В основном компилятор обеспечивает защиту от несчастных случаев, что очень полезно при разработке сложных программ.

<p id="AutBody_Root172"><strong>9.7.5. Члены и вспомогательные функции</strong></p>

  Разрабатывая минимальный (хотя и полный) интерфейс, мы вынуждены оставлять за бортом много полезных операций. Функцию, которая могла бы быть просто, элегантно и эффективно реализована как самостоятельная функция (т.е. не функция-член), следует реализовать за пределами класса. Таким образом, функция не сможет повредить данные, хранящиеся в объекте класса. Предотвращение доступа к данным является важным фактором, поскольку обычные методы поиска ошибок “вращаются вокруг типичных подозрительных мест”; иначе говоря, если с классом что-то не так, мы в первую очередь проверяем функции, имеющие прямой доступ к его представлению: одна из них обязательно является причиной ошибки. Если таких функций десяток, нам будет намного проще работать, чем если их будет пятьдесят.

Пятьдесят функций для класса Date! Возможно, вы думаете, что мы шутим. Вовсе нет: несколько лет назад я делал обзор нескольких коммерческих библиотек для работы с календарем и обнаружил в них множество функций вроде next_Sunday(), next_workday() и т.д. Пятьдесят — это совсем не невероятное число для класса, разработанного для удобства пользователей, а не для удобства его проектирования, реализации и сопровождения.

Отметим также, что если представление изменяется, то переписать достаточно только функции, которые имеют к ней прямой доступ. Это вторая важная практическая причина для минимизации интерфейса. Разрабатывая класс Date, мы могли решить, что дату лучше представлять в виде целого числа дней, прошедших с 1 января 1900 года, а не в виде тройки (год, месяц, день). В этом случае нам придется изменить только функции-члены.

Рассмотрим несколько примеров вспомогательных функций (helper functions).

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