Date sunday(2009,8,29); // инициализируем объект Sunday
// триадой (2009,8,29)
Функцию-член класса можно также определить в определении класса.
// простой класс Date (детали реализации будут рассмотрены позднее)
class Date {
public:
Date(int yy, int mm, int dd)
:y(yy), m(mm), d(dd)
{
// ...
}
void add_day(int n)
{
// ...
}
int month { return m; }
// ...
private:
int y, m, d; //
год, месяц, день
};
Во-первых, отметим, что теперь объявление класса стало больше и запутаннее. В данном примере код конструктора и функции
add_day
могут содержать десятки строк. Это в несколько раз увеличивает размер объявления класса и затрудняет поиск интерфейса среди деталей реализации. Итак, мы не рекомендуем определять большие функции в объявлении класса. Тем не менее посмотрите на определение функции
month
. Оно проще и короче, чем определение
Date::month
, размещенное за пределами объявления класса. Определения коротких и простых функций можно размещать в объявлении класса.
Обратите внимание на то, что функция
month
может обращаться к переменной m, даже несмотря на то, что переменная m определена позже (ниже) функции
month
. Член класса может ссылаться на другой член класса независимо от того, в каком месте класса он определен. Правило, утверждающее, что имя переменной должно быть объявлено до ее использования, внутри класса ослабляется.
Определение функции-члена в классе приводит к следующим последствиям.
• Функция становится подставляемой (inlined), т.е. компилятор попытается сгенерировать код подставляемой функции вместо ее вызова. Это может дать значительное преимущество часто вызываемым функциям, таким как
month
.
• При изменении тела подставляемой функции-члена класса придется скомпилировать заново все модули, в которых он используется. Если тело функции определено за пределами объявления класса, то потребуется перекомпилировать только само определение класса. Отсутствие необходимости повторного компилирования при изменении тела функции может оказаться огромным преимуществом в больших программах.
Очевидное правило гласит: не помещайте тела функций-членов в объявление класса, если вам не нужна повышенная эффективность программы за счет использования небольших подставляемых функций. Большие функции, скажем, состоящие из пяти и более строк, ничего не выиграют от подстановки. Не следует делать подставляемыми функции, содержащие более одного-двух выражений.
9.4.5. Ссылка на текущий объект
Рассмотрим простой пример использования
класса
Date
.
class Date {
// ...
int month { return m; }
// ...
private:
int y, m, d; // год, месяц, день
};
void f(Date d1, Date d2)
{
cout << d1.month << ' ' << d2.month << '\n';
}
Откуда функции
Date::month
известно, что при первом вызове следует вернуть значение переменной
d1.m
, а при втором —
d2.m
? Посмотрите на функцию
Date::month
еще раз; ее объявление не имеет аргумента! Как функция
Date::month
“узнает”, для какого объекта она вызывается? Функции-члены класса, такие как
Date::month
, имеют неявный аргумент, позволяющий идентифицировать объект, для которого они вызываются. Итак, при первом вызове переменная m правильно ссылается на
d1.m
, а при втором — на
d2.m
. Другие варианты использования неявного аргумента описаны в разделе 17.10.
9.4.6. Сообщения об ошибках
Что делать при обнаружении некорректной даты? В каком месте кода происходит поиск некорректных дат? В разделе 5.6 мы узнали, что в этом случае следует сгенерировать исключение, и самым очевидным местом для этого является место первого создания объекта класса
Date
. Если мы создали правильные объекты класса
Date
и все функции-члены написаны правильно, то мы никогда не получим объект класса
Date
с неверным значением. Итак, следует предотвратить создание неправильных объектов класса
Date
.
// простой класс Date (предотвращаем неверные даты)
class Date {
public:
class Invalid { }; // используется как исключение
Date(int y, int m, int d); // проверка и инициализация даты
// ...
private:
int y, m, d; // год, месяц, день
bool check; // если дата правильная, возвращает true
};
Мы поместили проверку корректности даты в отдельную функцию
check
, потому что с логической точки зрения эта проверка отличается от инициализации, а также потому, что нам может потребоваться несколько конструкторов. Легко видеть, что закрытыми могут быть не только данные, но и функции.
Date::Date(int yy, int mm, int dd)
:y(yy), m(mm), d(dd) // инициализация данных - членов класса