Эффективное использование C++. 55 верных способов улучшить структуру и код ваших программ | страница 25
>const CtextBlock cctb(“Hello”); // объявление константного объекта
>char &pc = &cctb[0]; // вызов const operator[] для получения
>// указателя на данные cctb
>*pc = ‘j’; // cctb теперь имеет значение “Jello”
Несомненно, есть что-то некорректное в том, что вы создаете константный объект с определенным значением, вызываете для него только константную функцию-член и тем не менее изменяете его значение!
Это приводит нас к понятию логической константности. Сторонники этой философии утверждают, что функции-члены с 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) – приводит к такому вот неуклюжему коду: