Несомненно, есть что-то некорректное в том, что вы создаете константный объект с определенным значением, вызываете для него только константную функцию-член и тем не менее изменяете его значение! Это приводит нас к понятию логической константности. Сторонники этой философии утверждают, что функции-члены с const могут модифицировать некоторые биты вызвавшего их объекта, но только так, чтобы пользователь не мог этого обнаружить. Например, ваш класс CTextBlock мог бы кэшировать длину текстового блока при каждом запросе:
Class CtextBlock {
public:
…
std::size_t length() const;
private:
char *pText;
std::size_t textLength; // последнее вычисленное значение длины
// текстового блока
bool lengthIsValid; // корректна ли длина в данный момент
};
std::size_t CtextBlock::length() const
{
if(!lengthIsValid) {
textLength = std::strlen(pText); // ошибка! Нельзя присваивать
lengthIsValid = true; // значение textLength и
} // lengthIsValid в константной
// функции-члене
return textLength;
}Эта реализация length(), конечно же, не является побитово константной, поскольку может модифицировать значения членов textLength и lengthlsValid. Но в то же время со стороны кажется, что константности объектов CTextBlock это не угрожает. Однако компилятор не согласен. Он настаивает на побитовой константности. Что делать? Решение простое: используйте модификатор mutable. Он освобождает нестатические данные-члены от ограничений побитовой константности:
Class CtextBlock {
public:
…
std::size_t length() const;
private:
char *pText;
mutable std::size_t textLength; // Эти данные-члены всегда могут быть
mutable bool lengthIsValid; // модифицированы, даже в константных
}; // функциях-членах
std::size_t CtextBlock::length() const
{
if(!lengthIsValid) {
textLength = std::strlen(pText); // теперь порядок
lengthIsValid = true; // здесь то же
}
return textLength;
}Как избежать дублирования в константных и неконстантных функциях-членах
Использование mutable – замечательное решение проблемы, когда побитовая константность вас не вполне устраивает, но оно не устраняет всех трудностей, связанных с const. Например, представьте, что operator[] в классе TextBlock (и CTextBlock) не только возвращает ссылку на соответствующий символ, но также проверяет выход за пределы массива, протоколирует информацию о доступе и, возможно, даже проверяет целостность данных. Помещение всей этой логики в обе версии функции operator[] – константную и неконстантную (даже если забыть, что теперь мы имеем необычно длинные встроенные функции – см. правило 30) – приводит к такому вот неуклюжему коду:
class TextBlock {
public:
…
const char& operator[](std::size_t position) const
{
… // выполнить проверку границ массива
… // протоколировать доступ к данным
… // проверить целостность данных
return text[position];
}
char& operator[](std::size_t position) const
{
… // выполнить проверку границ массива
… // протоколировать доступ к данным
… // проверить целостность данных
return text[position];
}
private:
std:string text;
};