Когда функция членства должна иметь спецификатор константы и когда не был должен он?

Приблизительно шесть лет назад разработчик программного обеспечения по имени Harri Porten написал эту статью, задав вопрос, "Когда функция членства должна иметь спецификатор константы и когда не был должен он?" Я нашел, что это было лучшей рецензией, которую я мог найти проблемы, с которой я боролся позже и которая я думаю, не хорошо покрыт большинством обсуждений, которые я нашел на правильности константы. Так как сайт совместного пользования информацией программного обеспечения, столь же мощный как ТАК, не существовал тогда, я хотел бы возродить вопрос здесь.

7
задан Mizipzor 17 March 2010 в 15:41
поделиться

6 ответов

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

Многие, вероятно, ответят: "Зависит.", но я бы хотел спросить: "Зависит от чего?"

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

Для примера, где это так, vector::operator[] возвращает ссылку на int. Ссылка на int является "частью" вектора, хотя на самом деле она не является членом данных. Таким образом, применяется идиома const-overload: измените элемент, и вы измените вектор.

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

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

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

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

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

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

на:

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

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

5
ответ дан 7 December 2019 в 01:19
поделиться

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

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

0
ответ дан 7 December 2019 в 01:19
поделиться

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

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

Исключения:

  • Логически конст-операции; методы, которые изменяют внутреннее состояние , но это изменение не обнаруживается с помощью интерфейса класса. Например, запросы дерева воспроизведения.
  • Методы, в которых реализации const/non-const отличаются только типом возвращаемого типа (как и методы, возвращаемые итератором/const_iterator). Вызов неконстной версии в версии const через const_cast допустим, чтобы избежать повторения.
  • Методы, сопряженные с 3-м сторонним C++, который не является правильным const, или с кодом, написанным на языке, который не поддерживает const
0
ответ дан 7 December 2019 в 01:19
поделиться

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

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

4
ответ дан 7 December 2019 в 01:19
поделиться

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

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

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

Я хотел бы продвинуть обсуждение еще на один шаг:

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

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

class Foo { void bar() const; };

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

class Foo { friend void bar(const Foo& self); }; // ALA Python
1
ответ дан 7 December 2019 в 01:19
поделиться

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

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

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

0
ответ дан 7 December 2019 в 01:19
поделиться
Другие вопросы по тегам:

Похожие вопросы: