15.4. Обеспечение невозможности модификации своих объектов в функции-члене
15.4. Обеспечение невозможности модификации своих объектов в функции-члене
Проблема
Требуется вызывать функции -члены для константного объекта, но ваш компилятор жалуется на то, что он не может преобразовать тип используемого вами объекта из константного в неконстантный.
Решение
Поместите ключевое слово const справа от имени функции-члена при ее объявлении в классе и при ее определении. Пример 15.4 показывает, как это можно сделать
Пример 15.4. Объявление функции-члена константной
#include <iostream>
#include <string>
class RecordSet {
public:
bool getFieldVal(int i, std::string& s) const;
// ...
};
bool RecordSet::getFieldVal(int i, std::string& s) const {
// Здесь нельзя модифицировать никакие неизменяемые
// данные-члены (см. обсуждение)
}
void displayRecords(const RecordSet& rs) {
// Здесь вы можете вызывать только константные функции-члены
// для rs
}
Обсуждение
Добавление концевого const в объявление члена и в его определение заставляет компилятор более внимательно отнестись к тому, что делается с объектом внутри тела члена. Константным функциям-членам не разрешается выполнять неконстантные операции с данными-членами. Если такие операции присутствуют, компиляция завершится неудачно. Например, если бы в RecordSet::getFieldVal я обновил счетчик-член, эта функция не была бы откомпилирована (в предположении, что getFieldCount_ является переменной-членом класса RecordSet).
bool RecordSet::getFieldVal(int i, std::string& s) const {
++getFieldCount_; // Ошибка: константная функция-член не может
// модифицировать переменную-член
// ...
}
Это может также помочь обнаружить более тонкие ошибки, подобно тому, что делает const в роли квалификатора переменной (см. рецепт 15.3). Рассмотрим следующую глупую ошибку.
bool RecordSet::getFieldVal(int i, std::string& s) const {
fieldArray_[i] = s; // Ой, я не это имел в виду
// ...
}
Снова компилятор преждевременно завершит работу и выдаст сообщение об ошибке, потому что вы пытаетесь изменить переменную-член, а это не разрешается делать в константных функциях-членах. Ну, при одном исключении.
В классе RecordSet (в таком, как (схематичный) класс в примере 15.4) вам, вероятно, потребовалось бы перемещаться туда-сюда по набору записей, используя понятие «текущей» записи. Простой способ заключается в применении переменной-члена целого типа, содержащей номер текущей записи; ваши функции-члены, предназначенные для перемещения текущей записи вперед-назад, должны увеличивать или уменьшать это значение.
void RecordSet::gotoNextPecord() const {
if (curIndex_ >= 0 && curIndex_ < numRecords_-1)
++curIndex_;
}
void RecordSet::gotoPrevRecord() const {
if (curIndex_ > 0)
--curIndex_;
}
Очевидно, что это не сработает, если эти функции-члены являются константными. Обе обновляют данное-член. Однако без этого пользователи класса RecordSet не смогут перемещаться по объекту const RecordSet. Это исключение из правил работы с константными функциями-членами является вполне разумным, поэтому C++ имеет механизм его поддержки: ключевое слово mutable.
Для того чтобы curIndex_ можно было обновлять в константной функции-члене, объявите ее с ключевым словом mutable в объявлении класса.
mutable int curIndex_;
Это позволит вам модифицировать curIndex_ в любом месте. Однако этой возможностью следует пользоваться разумно, поскольку это действует на вашу функцию так, как будто она становится с этого момента неконстантной.
Применение ключевого слова const в примере 15.4 позволяет гарантировать невозможность изменения состояния объекта в функции-члене. В целом, такой подход дает хорошие результаты, потому что сообщает пользователям класса о режиме работы функции-члена и потому что сохраняет вам репутацию, заставляя компилятор проконтролировать отсутствие в функции-члене непредусмотренных действий.