Выбрать главу

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

void concatUnsafePtr(std::string* ps1,

 std::string* ps2, std::string* pout) {

 *pout = *ps1 + *ps2;

}

Здесь такая же проблема, как в примере с concatUnsafe, описанном ранее. Добавьте const для гарантии невозможности обновления исходных строк.

void concatSaferPtr(const std::string* ps1,

 const std::string* ps2, std::string* pout) {

 *pout = *ps1 + *ps2;

}

Отлично, теперь вы не можете изменить *ps1 и *ps2. Но вы по-прежнему можете изменить ps1 и ps2, или, другими словами, используя их, вы можете сослаться на какую-нибудь другую строку, изменяя значение указателя, но не значение, на которое он ссылается. Ничто не может помешать вам, например, сделать следующее.

void concatSaferPtr(const std:string* ps1,

 const std::string* ps2, std::string* pout) {

 ps1 = pout; // Ух!

 *pout = *ps1 + *ps2;

}

Предотвратить подобные ошибки можно с помощью еще одного const.

void concatSafestPtr(const std::string* const ps1,

 const std::string* const ps2, std::string* pout) {

 *pout = *ps1 + *ps2;

}

Применение const по обе стороны звездочки делает вашу функцию максимально надежной. В этом случае вы ясно показываете свои намерения пользователям вашей функции, и ваша репутация не пострадает в случае описки.

См. также

Рецепт 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 позволяет гарантировать невозможность изменения состояния объекта в функции-члене. В целом, такой подход дает хорошие результаты, потому что сообщает пользователям класса о режиме работы функции-члена и потому что сохраняет вам репутацию, заставляя компилятор проконтролировать отсутствие в функции-члене непредусмотренных действий.

15.5. Написание оператора, не являющегося функцией-членом

Проблема

Необходимо написать бинарный оператор, и вы не можете или не хотите сделать его функцией-членом класса.

Решение

Используйте ключевое слово operator, временную переменную и конструктор копирования для выполнения основной работы и возвратите временный объект. В примере 15.5 приводится простой оператор конкатенации строк для пользовательского класса String.