Когда функция-член должна иметь квалификатор const, а когда - нет? - PullRequest
7 голосов
/ 17 марта 2010

Около шести лет назад инженер-программист по имени Харри Портен написал эту статью , задав вопрос: "Когда функция-член должна иметь квалификатор const, а когда - нет?" Я нашел, что это лучшая статья, которую я смог найти по проблеме, с которой я боролся совсем недавно и которая, я думаю, не очень хорошо освещена в большинстве дискуссий, которые я нашел о правильности const. Поскольку в то время такого мощного программного сайта, как SO, не существовало, я хотел бы воскресить этот вопрос здесь.

Ответы [ 6 ]

5 голосов
/ 17 марта 2010

Статья, кажется, охватывает много базовых вопросов, но у автора все еще остается вопрос о постоянных и неконстантных перегрузках функций, возвращающих указатели. Последняя строка статьи:

Многие, вероятно, ответят: «Это зависит». но я хотел бы спросить: «Это зависит от чего?»

Если быть точным, это зависит от того, является ли состояние объекта-объекта A логически частью состояния this объекта.

Например, vector<int>::operator[] возвращает ссылку на int. INT referand является «частью» вектора, хотя на самом деле он не является членом данных. Таким образом, применяется идиома const-overload: измените элемент, и вы изменили вектор.

Для примера, где это не так, рассмотрим shared_ptr. Он имеет функцию-член T * operator->() const;, поскольку логично иметь умный указатель const на неконстантный объект . Ссылка не является частью умного указателя: изменение его не приводит к изменению умного указателя. Таким образом, вопрос о том, можете ли вы «переустановить» умный указатель для ссылки на другой объект, не зависит от того, является ли реферат постоянным.

Не думаю, что смогу дать какие-либо полные рекомендации, которые позволят вам решить, является ли пуантист логически частью объекта или нет. Однако, если изменение указателя изменяет возвращаемые значения или другое поведение каких-либо функций-членов this, и особенно если оно участвует в operator==, то, скорее всего, оно логически является частью this объекта.

Я бы ошибся, если предположить, что является частью (и обеспечить перегрузки). Затем, если возникла ситуация, когда компилятор жалуется, что я пытаюсь изменить объект A, возвращенный из объекта const, я бы подумал, действительно ли я должен это делать или нет, и если да, то измените дизайн так, чтобы только указатель на A концептуально является частью состояния объекта, а не самого A. Это, конечно, требует, чтобы изменение A не делало ничего, что нарушало бы ожидаемое поведение this const объекта.

Если вы публикуете интерфейс, вам, возможно, придется выяснить это заранее, но на практике переход от константных перегрузок к const-function-returning-non-const-pointer вряд ли нарушит клиентский код. В любом случае, к тому времени, когда вы публикуете интерфейс, вы, надеюсь, уже немного его использовали и, вероятно, почувствовали, что на самом деле включает в себя состояние вашего объекта.

Кстати, я также пытаюсь ошибиться из-за того, что не предоставляю средства доступа к указателям / ссылкам, особенно модифицируемые. Это действительно отдельная проблема (Закон Деметры и все такое), но чем больше раз вы можете заменить:

A *getA();
const A *getA() const;

с:

A getA() const; // or const A &getA() const; to avoid a copy
void setA(const A &a);

Чем меньше раз вы будете беспокоиться о проблеме. Конечно, у последнего есть свои ограничения.

4 голосов
/ 17 марта 2010

Одно интересное эмпирическое правило, которое я нашел во время исследования, пришло от здесь :

Хорошее практическое правило для LogicalConst следующее: если операция сохраняет LogicalConstness, то, если старое состояние и новое состояние сравниваются с EqualityOperator, результат должен быть истинным. Другими словами, EqualityOperator должен отражать логическое состояние объекта.

1 голос
/ 17 марта 2010

Я лично использую очень простое правило:

Если наблюдаемое состояние объекта не изменяется при вызове данного метода, этот метод должен быть const.

В целом, это похоже на правило, упомянутое SCFrench о Сравнении Равенства, за исключением того, что большинство моих классов нельзя сравнивать.

Я хотел бы продвинуть дискуссию на один шаг вперед, хотя:

Когда требуется аргумент, функция должна принимать его с помощью const дескриптора (или копии), если аргумент остается неизменным (для внешнего наблюдателя)

Это несколько более общий термин, поскольку в конце концов метод класса является ничем иным, как автономной функцией, принимающей экземпляр класса в качестве первого аргумента:

class Foo { void bar() const; };

эквивалентно:

class Foo { friend void bar(const Foo& self); }; // ALA Python
0 голосов
/ 17 марта 2010

Вот несколько хороших статей:
Трава Саттера GotW # 6
Herb Sutter & const для оптимизации
Еще советы по правильности констант
Из Википедии

Я использую const квалификаторы метода, когда метод не изменяет членов данных класса или его общая цель - не изменять члены данных. Один из примеров включает RAII для метода getter , который может потребоваться для инициализации элементов данных (например, для извлечения из базы данных). В этом примере метод изменяет элемент (ы) данных только один раз во время инициализации; в остальное время оно постоянно.

Я разрешаю компилятору перехватывать const ошибок во время компиляции, а не перехватывать их во время выполнения (или пользователь).

0 голосов
/ 17 марта 2010

Общее правило:

Функция-член должна иметь значение const, если она компилируется, если помечены как const , и , если она все равно будет компилироваться, если const были транзитивными указателями w.r.t.

Исключения:

  • Логически константные операции; методы, которые изменяют внутреннее состояние , но это изменение невозможно обнаружить с помощью интерфейса класса . Например, запросы к Splay Tree.
  • Методы, в которых реализации const / non-const отличаются только типом возвращаемого значения (обычно с методами return iterator / const_iterator). Вызов неконстантной версии в константной версии через const_cast допустим, чтобы избежать повторения.
  • Методы взаимодействия с C ++ сторонних производителей, которые не являются правильными, или с кодом, написанным на языке, который не поддерживает const
0 голосов
/ 17 марта 2010

, когда объект не изменяется.

Это просто заставляет this иметь тип const myclass*. Это гарантирует вызывающей функции, что объект не изменится. Позволяет некоторую оптимизацию компилятору, и программисту легче узнать, может ли он вызывать ее без побочных эффектов (по крайней мере, для объекта).

...