Эффективное использование C++. 55 верных способов улучшить структуру и код ваших программ - [13]

Шрифт
Интервал


>tb[0] = ‘x’;


Это объясняется тем, что возвращаемое функцией значение встроенного типа модифицировать некорректно. Даже если бы это было допустимо, тот факт, что C++ возвращает объекты по значению (см. правило 20), означал бы следующее: модифицировалась копия tb.text[0], а не само значение tb.text[0]. Вряд ли это то, чего вы ожидаете.

Давайте немного передохнем и пофилософствуем. Что означает для функции-члена быть константной? Существует два широко распространенных понятия: побитовая константность (также известная как физическая константность) и логическая константность.

Сторонники побитовой константности полагают, что функция-член константна тогда и только тогда, когда она не модифицирует никакие данные-члены объекта (за исключением статических), то есть не модифицирует ни одного бита внутри объекта. Определение побитовой константности хорошо тем, что ее нарушение легко обнаружить: компилятор просто ищет присваивания членам класса. Фактически, побитовая константность – это константность, определенная в C++: функция-член с модификатором const не может модифицировать нестатические данные-члены объекта, для которого она вызвана.

К сожалению, многие функции-члены, которые ведут себя далеко не константно, проходят побитовый тест. В частности, функция-член, которая модифицирует то, на что указывает указатель, часто не ведет себя как константная. Но если объекту принадлежит только указатель, то функция формально является побитово константной, и компилятор не станет возражать. Это может привести к неожиданному поведению. Например, предположим, что есть класс подобный Text-Block, где данные хранятся в строках типа char * вместо string, поскольку это необходимо для передачи в функции, написанные на языке C, который не понимает, что такое объекты типа string.


>class CtextBlock {

>public:

>...

>char& operator[](std::size_t position) const // неудачное (но побитово

>{ return pText[position]} // константное)

>// объявление operator[]

>private:

>char *pText;

>};


В этом классе функция operator[] (неправильно!) объявлена как константная функция-член, хотя она возвращает ссылку на внутренние данные объекта (эта тема обсуждается в правиле 28). Оставим это пока в стороне и отметим, что реализация operator[] никак не модифицирует pText. В результате компилятор спокойно сгенерирует код для функции operator[]. Ведь она действительно является побитово константной, а это все, что компилятор может проверить. Но посмотрите, что происходит:


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


Рекомендуем почитать
Язык PL/SQL

В учебно-методическом пособии рассматриваются основы языка программирования PL/SQL, реализованного в системе управления базами данных Oracle Database Server. Приводятся сведения о поддерживаемых типах данных, структуре программ PL/SQL и выполнении SQL-предложений в них. Отдельно рассмотрено создание хранимых в базах данных Oracle программ PL/SQL – процедур, функций, пакетов и триггеров.


Пишем драйвер Windows на ассемблере

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


Язык программирования С# 2005 и платформа .NET 2.0.

В этой книге содержится описание базовых принципов функционирования платформы .NET, системы типов .NET и различных инструментальных средств разработки, используемых при создании приложений .NET. Представлены базовые возможности языка программирования C# 2005, включая новые синтаксические конструкции, появившиеся с выходом .NET 2.0, а также синтаксис и семантика языка CIL. В книге рассматривается формат сборок .NET, библиотеки базовых классов .NET. файловый ввод-вывод, возможности удаленного доступа, конструкция приложений Windows Forms, доступ к базам данных с помощью ADO.NET, создание Web-приложений ASP.NET и Web-служб XML.


Вариации на тему STL. Адаптер обобщенного указателя на функцию-член класса

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


Информационная технология. Руководство по управлению документированием программного обеспечения

ГОСУДАРСТВЕННЫЙ СТАНДАРТ РОССИЙСКОЙ ФЕДЕРАЦИИИнформационная технологияРУКОВОДСТВО ПО УПРАВЛЕНИЮ ДОКУМЕНТИРОВАНИЕМ ПРОГРАММНОГО ОБЕСПЕЧЕНИЯInformation technology. Guidelines for the management of software documentationИздание официальноеДата введения 1994-07-01ГОССТАНДАРТ РОССИИ Москва© Издательство стандартов, 1994.


Самоучитель UML

Самоучитель UMLПервое издание.В книге рассматриваются основы UML – унифицированного языка моделирования для описания, визуализации и документирования объектно-ориентированных систем и бизнес-процессов в ходе разработки программных приложений. Подробно описываются базовые понятия UML, необходимые для построения объектно-ориентированной модели системы с использованием графической нотации. Изложение сопровождается примерами разработки отдельных диаграмм, которые необходимы для представления информационной модели системы.