const - когда быть, когда не быть

Автор: Андрей Тарасевич
Опубликовано: 30.10.2004
Версия текста: 1.0
Версия текста: 1.0

Q. Довольно продолжительное время ломаю голову - когда делать функцию константной, а когда в этом нет необходимости???

A. Однозначного "механического" ответа на этот вопрос не существует. Зачастую в качестве "механического" ответа на этот вопрос приводят что-то вроде "если функция не меняет содержимого класса, то ее следует сделать константной, в противном случае – неконстантной", где под словом "меняет" подразумевается фактическое изменение одного из внутренних полей класса. Во многих случаях это правило дает верный (или почти верный) ответ на поставленный вопрос. Но на самом деле в общем случае это правило является лишь простейшим первым приближением к правильному ответу – оно реализует идею физической константности. На самом деле язык С++ более-менее приспособлен к реализации идеи логической константности. Делать метод класса константным или нет – вопрос, который должен решаться на уровне дизайна класса и проекта.

У класса есть некоторое состояние, которое видно извне через интерфейс этого класса. Константными должны быть методы, которые не меняют этого состояния. Соответственно, с модификатором 'const' должны объявляться те объекты этого класса, которые не допускают изменения этого состояния. Состояние в данном случае совсем не эквивалентно банальному бинарному содержимому полей объекта. Поля могут изменяться – а состояние при этом оставаться тем же.

Классический пример – класс некого ассоциативного хранилища (например, массива), реализованного с использованием кэширования запросов. При поступлении запроса сначала производится поиск в небольшом кэше, а затем, если поиск в кэше ничего не дал, уже собственно в хранилище. Результат поиска возвращается наружу, а также добавляется в кэш последних запросов. Если поиск в собственно хранилище – дорогая операция, а одинаковые запросы часто приходят подряд (или почти подряд), наличие совсем небольшого кэша может дать огромный выигрыш в производительности. Но с точки зрения интерфейса класса – это не более чем ассоциативное хранилище. Наличие кэша внутри этого класса не является частью его интерфейсной спецификации. Снаружи о кэше никто не знает и не должен знать. Кэша может и не быть вообще – интерфейсная функциональность класса от этого не пострадает (может пострадать производительность, но не функциональность). В такой ситуации состояние кэша не является частью интерфейсного состояния класса. Операция поиска данных по ключу в хранилище не меняет состояния класса и поэтому соответствующий метод класса следует объявить как 'const'. В то же время каждая операция поиска потенциально меняет содержимое кэша, хранящегося внутри этого класса. Т.е. данное использование модификатора 'const' противоречит идее физической константности, но нисколько не противоречит идее логической константности. Проблемы противоречия с идеей физической константности (о которых нам, скорее всего, не забудет сообщить компилятор) легко устраняются при помощи объявления самого кэша с модификатором 'mutable'. Именно для этого модификатор 'mutable' и предназначался.

Предыдущий пример демонстрирует ситуацию, когда физическая константность требует отсутствия модификатора 'const' в объявлении метода, в то время как логическая константность говорит, что модификатор 'const' нужен. Возможна и обратная ситуация. Пусть некоторый класс 'P' содержит в себе поле 'sptr' типа 'указатель на объект класса S'. Пусть также в классе 'P' есть некий метод 'P::ChangeS()', модифицирующий указываемый полем 'sptr' объект класса 'S', но ничего не меняющий в объекте класса 'P'. Должен ли этот метод быть константным? С точки зрения физической константности – да, должен (или, по крайней мере, может), ибо физически объект класса 'P' никак не меняется методом 'P::ChangeS()'. Константность объекта типа 'P' в С++ формально никак не распространяется на указываемый полем 'sptr' объект типа 'S', поэтому объявление 'P::ChangeS()' с модификатором 'const' никак не помешает поменять последний. А вот на логическом уровне все не так просто. Все зависит от того, какой принцип дизайна реализуется наличием в классе 'P' этого указателя 'sptr'. Это агрегация? Или просто невинная ссылка? Если это агрегация, и состояние указываемого объекта класса 'S' как-то влияет на состояние объекта класса 'P', то с точки зрения дизайна изменение состояния указываемого объекта означает изменение состояния и объекта класса 'P' тоже. В таком случае метод 'P::ChangeS()' не должен иметь в своем объявлении модификатора 'const'. Если же указатель 'sptr' реализует просто неагрегирующую ссылку от 'P' к 'S', то можно объявить 'P::ChangeS()' с модификатором 'const'.

Вот такие вот дела. Константность в дизайне программы – понятие логическое, а не физическое. Решения о том, какие операции являются константными, а какие – нет, принимаются уровне дизайна (программы, модуля или отдельного класса), и на основе этих решений затем делается расстановка модификаторов 'const' в объявлениях методов, параметров, объектов и т.д. в процессе реализации.


Эта статья опубликована в журнале RSDN Magazine #4-2004. Информацию о журнале можно найти здесь