Эффективное использование 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) – приводит к такому вот неуклюжему коду: