Как избежать дублирования в константных и неконстантных функциях-членах
Как избежать дублирования в константных и неконстантных функциях-членах
Использование 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;
};
Ох! Налицо все неприятности, связанные с дублированием кода: увеличение времени компиляции, размера программы и неудобство сопровождения. Конечно, можно переместить весь код для проверки выхода за границы массива и прочего в отдельную функцию-член (естественно, закрытую), которую будут вызывать обе версии operator[], но обращения к этой функции все же будут дублироваться.
В действительности было бы желательно реализовать функциональность operator[] один раз, а использовать в двух местах. То есть одна версия operator[] должна вызывать другую. И это подводит нас к вопросу об отбрасывании константности.
С самого начала отметим, отбрасывать константность нехорошо. Я посвятил целое правило 27 тому, чтобы убедить вас не делать этого, но дублирование кода – тоже не сахар. В данном случае константная версия operator[] делает в точности то же самое, что неконстантная, и отличие между ними – лишь в присутствии модификатора const. В этой ситуации отбрасывать const безопасно, поскольку пользователь, вызывающий неконстантный operator[], так или иначе должен получить неконстантный объект. Ведь в противном случае он не стал бы вызывать неконстантную функцию. Поэтому реализация неконстантного operator[] путем вызова константной версии – это безопасный способ избежать дублирования кода, даже пусть даже для этого требуется воспользоваться оператором const_cast. Ниже приведен получающийся в результате код, но он станет яснее после того, как вы прочитаете следующие далее объяснения:
class TextBlock {
public:
...
const char& operator[](std::size_t position) const // то же, что и раньше
{
...
...
...
return text[position];
}
char& operator[](std::size_t position) const // теперь просто
// вызываем const op[]
{
return
const_cast<char&>( // из возвращаемого типа
// op[] исключить const
static_cast<const TextBlock&>(*this) // добавить const типу
// *this
[position] // вызвать константную
); // версию op[]
}
...
};
Как видите, код включает два приведения, а не одно. Мы хотим, чтобы неконстантный operator[] вызывал константный, но если внутри неконстантного оператора [] просто вызовем operator[], то получится рекурсивный вызов. Во избежание бесконечной рекурсии нужно указать, что мы хотим вызвать const operator[], но прямого способа сделать это не существует. Поэтому мы приводим *this от типа TextBlock& к const TextBlock&. Да, мы выполняем приведение, чтобы добавить константность! Таким образом, мы имеем два приведения: одно добавляет константность *this (чтобы был вызван const operator[]), а второе – исключает const из типа возвращаемого значения.
Приведение, которое добавляет const, выполняет безопасное преобразование (от неконстантного объекта к константному), поэтому мы используем для этой цели static_cast. Приведение же, которое отбрасывает const, может быть выполнено только с помощью const_cast, поэтому у нас здесь нет выбора. (Строго говоря, выбор есть. Приведение в стиле C также работает, но, как я объясняю в правиле 27, такие приведения редко являются правильным рещением. Если вы не знакомы с операторами static_cast или const_cast, прочитайте о них в правиле 27.)
Помимо всего прочего, в этом примере мы вызываем оператор, поэтому синтаксис выглядит немного странно. Возможно, этот код не займет приз на конкурсе красоты, зато позволяет достичь нужного эффекта – избежать дублирования посредством реализации неконстантной версии operator[] в терминах константной. И хотя для достижения цели пришлось воспользоваться неуклюжим синтаксисом, который сможете понять только вы сами, однако техника реализации неконстантных функций-членов через неконстантные определенно заслуживает того, чтобы ее знать.
А еще нужно иметь в виду, что решать эту задачу наоборот – путем вызова неконстантной версии из константной – неправильно. Помните, что константная функция-член обещает никогда не изменять логическое состояние объекта, а неконстантная не дает таких гарантий. Если вы вызовете неконстантную функцию из константной, то рискуете получить ситуацию, когда объект, который не должен модифицироваться, будет изменен. Вот почему этого не следует делать: чтобы объект не изменился. Фактически, чтобы получить компилируемый код, вам пришлось бы использовать const_cast для отбрасывания константности *this, а это явный признак неудачного решения. Обратная последовательность вызовов – такая, как описана выше, – безопасна. Неконстантная функция-член может делать все, что захочет с объектом, поэтому вызов из нее константной функции-члена ничем не грозит. Потому-то мы и применяем к *this оператор static_cast, отбрасывания константности при этом не происходит.
Как я уже упоминал в начале этого правила, модификатор const – чудесная вещь. Для указателей и итераторов; для объектов, на которые ссылаются указатели, итераторы и ссылки; для параметров функций и возвращаемых ими значений; для локальных переменных, для функций-членов – всюду const ваш мощный союзник. Используйте его, где только возможно. Вам понравится!
Что следует помнить
• Объявление чего-либо с модификатором const помогает компиляторам обнаруживать ошибки. const можно использовать с объектами в любой области действия, с параметрами функций и возвращаемых значений, а также с функциями-членами в целом.
• Компиляторы проверяют побитовую константность, но вы должны программировать, применяя логическую константность.
• Когда константные и неконстантные функции-члены имеют, по сути, одинаковую реализацию, то дублирования кода можно избежать, заставив неконстантную версию вызывать константную.
Более 800 000 книг и аудиокниг! 📚
Получи 2 месяца Литрес Подписки в подарок и наслаждайся неограниченным чтением
ПОЛУЧИТЬ ПОДАРОКДанный текст является ознакомительным фрагментом.
Читайте также
Как избежать проблем с выравниванием
Как избежать проблем с выравниванием Компилятор обычно предотвращает проблемы, связанные с выравниванием, путем естественного выравнивания всех типов данных. На самом деле, разработчики ядра обычно не должны заниматься проблемами, связанными с выравниванием, об этом
Замечания о функциях установления соединения
Замечания о функциях установления соединения Перед тем как углубиться в отдельные сообщения, однако, есть смысл подчеркнуть, что для всех функции установления соединения структура сообщений идентична (взято из <sys/iomsg.h>, с небольшими изменениями):struct _io_connect { // Для
Как избежать давления
Как избежать давления Лучший способ сохранять спокойствие под давлением – избегать ситуаций, создающих давление. Возможно, это не решит проблему полностью, но по крайней мере сведет к минимуму и сократит продолжительность напряженных
Понятие о функциях
Понятие о функциях И уж совсем в заключение этого раздела осталось сказать пару слов о функциях командной оболочки. Это — такая же последовательность команд (или даже просто одиночная команда), как и сценарий, но — не вынесенная в отдельный исполняемый файл, а помещённая
23.3.4 Обнаружение дублирования IP-адресов
23.3.4 Обнаружение дублирования IP-адресов Перед использованием IP-адреса локальной связи или любого другого адреса, который не был построен путем добавления префикса к адресу локальной связи, узел должен послать сообщение Neighbor Solicitation с целью узнать, имеет ли кто-то из
Как избежать обмана при приеме на работу в Интернете
Как избежать обмана при приеме на работу в Интернете Интернет позволяет искать работу не отходя от компьютера и работать на дому. Оба этих случая не остались без внимания мошенников. Первый способ напоминает «нигерийские» письма. Объекту предлагается
Как избежать SQL
Как избежать SQL Добавление инструкций SQL в код VBA чем-то сродни изучению письма, содержащего отрывки на греческом языке, сложные математические вычисления и музыкальное представление. Несмотря на то, что SQL более компактный и узконаправленный язык программирования, чем
ИСПОЛЬЗОВАНИЕ НАШИХ ЗНАНИЙ О ФУНКЦИЯХ
ИСПОЛЬЗОВАНИЕ НАШИХ ЗНАНИЙ О ФУНКЦИЯХ Теперь, когда мы знаем о функциях немного больше, соберем вместе несколько поучительных примеров, но сначала решим, чем мы будет заниматься. Что вы скажете насчет функции возведения в степень, которая дает возможность
8.4.4. Динамическое создание и уничтожение константных объектов
8.4.4. Динамическое создание и уничтожение константных объектов Программист способен создать объект в хипе и запретить изменение его значения после инициализации. Этого можно достичь, объявляя объект константным. Для этого применяется следующая форма оператора new:const int
Предупреждение дублирования записей при вводе их из формы
Предупреждение дублирования записей при вводе их из формы В главе 11 мы обсуждали вопрос об очистке базы данных от повторяющихся записей, которые попали в таблицы, и выяснили, что этот механизм может работать и в профилактическом режиме, предотвращая попадание дубликатов
5.2.3. Добавление элемента к списку, если он в нем отсутствует (добавление без дублирования)
5.2.3. Добавление элемента к списку, если он в нем отсутствует (добавление без дублирования) Часто требуется добавлять элемент X в список L только в том случае, когда в списке еще нет такого элемента. Если же X уже есть в L, тогда L необходимо оставить без изменения, поскольку
Ошибки автофокуса и как их избежать
Ошибки автофокуса и как их избежать Камера с единственным датчиком в режиме точечного замера может растеряться. К примеру, если в кадре беседуют два или три человека, датчик может сфокусироваться на промежутке между ними. При этом люди на снимке «расплывутся», а фон,
Как избежать соблазнительных затрат времени
Как избежать соблазнительных затрат времени Против некоторых видов непроизводительных затрат времени просто невозможно устоять. Здесь все сводится к правилу «знай меру». Короткая беседа у автомата с газировкой прервет монотонный рабочий день, позволяя вам вернуться
Туннельный синдром запястья: как его избежать
Туннельный синдром запястья: как его избежать При работе за компьютером может возникнуть еще одна проблема, менее очевидная, однако часто приводящая к серьезным негативным последствиям.«Просто руки отваливаются!» – это обязательно когда–нибудь произносил
Глава 4 Как избежать ошибок при съемке
Глава 4 Как избежать ошибок при съемке • «Шевеленка»• Смазанный движущийся объект• Наклонный горизонт• Кадрирование• Главный объект не в фокусе• Лишнее в кадре• Из головы растут «рога»• Ошибки при съемке портрета крупным планом на открытом воздухеОшибки, так легко