Это хорошая практика, чтобы добавить const в конце функций-членов - где это уместно? - PullRequest
13 голосов
/ 29 апреля 2011

Является ли хорошей практикой в ​​C ++ добавление const в конце определения функции-члена каждый раз, когда функция не изменяет объект, т. Е. Каждый раз, когда функция 'подходит' для Const? Я знаю, что это необходимо в этом случае:

class MyClass {

public:
int getData() const;
};

void function(const MyClass &m) { int a = m.getData(); dosomething... }

, но кроме этого, и другого использования const для реальной функциональности, действительно ли добавление const в конец фактически изменяет способ выполнения кода (быстрее / медленнее), или это просто «флаг» для компилятора для обработки таких случаев, таких как как тот, что выше? Другими словами, если const (в конце) не требуется для функциональности в классе, имеет ли это какое-то значение?

Ответы [ 7 ]

17 голосов
/ 29 апреля 2011

Пожалуйста, посмотрите эту отличную статью о правильности констант Херба Саттера (секретарь комитета по стандартам C ++ на 10 лет.)

Что касается оптимизаций, он позже написал эту статью где он утверждает, что «const в основном предназначен для людей, а не для компиляторов и оптимизаторов».Оптимизация невозможна, потому что «слишком много вещей может пойти не так ... [ваша функция] может выполнить const_casts».

Однако правильность const хорошая идея по двум причинам: она дешевая (с точки зрения вашеговремя) утверждение, что можно найти ошибки;и это сигнализирует о том, что функция теоретически не должна изменять внешнее состояние объекта , что облегчает понимание кода.

5 голосов
/ 29 апреля 2011

каждый раз, когда функция не изменяет объект, т. Е. Каждый раз, когда функция 'имеет право' на const?

На мой взгляд, Да.Это гарантирует, что вы вызываете такие функции для const объектов или const выражений, связанных с объектом:

void f(const A & a)
{
   a.inspect(); //inspect must be a const member function.
}

Даже если он изменяет одну или несколько внутренних переменных один или два раза, даже тогда я обычно делаю это const функция-член.И эти переменные объявлены с ключевым словом mutable:

class A
{
     mutable bool initialized_cache; //mutable is must!

     public:
         void inspect() const //const member function
         {
               if ( !initialized_cache)
               {
                    initialized_cache= true;
                    //todo: initialize cache here
               }
               //other code
         }
};
3 голосов
/ 29 апреля 2011

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

Причина, по которой невероятно важно использовать слово const:

  1. Этоважная документация для других разработчиков.Разработчики будут предполагать, что что-либо помеченное как const не изменяет объект (вот почему не может быть хорошей идеей использовать const при изменении состояния через объект-указатель), и будут предполагать, что все не помеченное как const мутирует.

  2. Это заставит компилятор перехватывать непреднамеренные мутации (вызывая ошибку, если функция, помеченная как const, непреднамеренно вызывает неконстантную функцию или мутирует элемент).

1 голос
/ 29 апреля 2011

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

Например, если функция, которая возвращает фиксированную строку 'Some text', написана как

char *Function1()
{ return “Some text”;}

тогда программа может аварийно завершить работу, если она случайно попытается изменить значение, выполнив

Function1()[1]=’a’;

, тогда как компилятор обнаружит ошибку, если будет написана исходная функция

 const char *Function1()
 { return "Some text";}

потому что компилятор будет знать, что значение было неизменным.(Конечно, компилятор теоретически мог бы решить это в любом случае, но C не настолько умен.) Когда подпрограмма или функция вызывается с параметрами, переменные, переданные как параметры, могут быть прочитаны для передачи данных в подпрограмму / функцию, записаннуючтобы передать данные обратно в вызывающую программу или и то, и другое, чтобы сделать оба.Некоторые языки позволяют указывать это напрямую, например, иметь типы параметров «in:», «out:» и «inout:», тогда как в C нужно работать на более низком уровне и указывать метод передачи переменных, выбирая одинэто также допускает желаемое направление передачи данных.

Например, подпрограмма, такая как

void Subroutine1(int Parameter1)
{ printf("%d",Parameter1);}

, принимает параметр, переданный ей по умолчанию на C & C ++, который является копией.Поэтому подпрограмма может считывать значение переменной, переданной ей, но не изменять ее, потому что любые изменения, которые она делает, вносятся только в копию и теряются, когда подпрограмма заканчивается, поэтому

void Subroutine2(int Parameter1)
{ Parameter1=96;}

оставит переменную, которой она былаВызван с неизменным, не установленным в 96.

Добавление символа '&' к имени параметра в C ++ (что было очень запутанным выбором символа, потому что знак '&' перед переменными в других местах в C генерирует указатели!) like вызывает использование самой переменной, а не копии, в качестве параметра в подпрограмме и, следовательно, может быть записано для передачи данных обратно в подпрограмму.Поэтому

void Subroutine3(int &Parameter1) 
{ Parameter1=96;}

установит для переменной, с которой она была вызвана, значение 96. Этот метод передачи переменной как самой, а не как копии, называется "ссылкой" в C.

Таким образомпередача переменных была дополнением C ++ к C. Для передачи изменяемой переменной в исходном C использовался довольно сложный метод, использующий указатель на переменную в качестве параметра, а затем изменяющий то, на что он указывал.Например,

void Subroutine4(int *Parameter1) 
{ *Parameter1=96;}

работает, но требует, чтобы каждое использование переменной в вызываемой подпрограмме изменялось, и вызывающая подпрограмма изменялась для передачи указателя на переменную, что довольно громоздко.

Нооткуда в этом слово «const»?Ну, есть второе распространенное использование для передачи данных по ссылке или указателю вместо копирования.То есть, когда копирование переменной будет тратить слишком много памяти или займет слишком много времени.Это особенно вероятно для больших составных пользовательских типов переменных («структуры» в C и «классы» в C ++).Так что подпрограмма, объявленная

void Subroutine4(big_structure_type &Parameter1);

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

Для решения этой проблемы можно использовать «const» в списке параметров, например

void Subroutine4(big_structure_type const &Parameter1);

, что приведет к тому, что переменная будет передана без копирования, но остановит ее от изменения. Это грязно, потому что он по сути делает метод передачи переменных in-only из метода передачи переменных в обоих направлениях, который сам был создан из метода передачи переменных in-only только для того, чтобы заставить компилятор выполнить некоторую оптимизацию.

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

1 голос
/ 29 апреля 2011

Да, это хорошая практика.

На уровне разработки программного обеспечения это позволяет вам иметь объекты только для чтения, например, вы можете предотвратить изменение объектов, сделав их постоянными.И если объект является константным, вам разрешено вызывать только константные функции для него.

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

0 голосов
/ 29 апреля 2011

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

0 голосов
/ 29 апреля 2011

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

void function(const MyClass& foo)
{
   foo.getData();
}

Вы столкнетесь с проблемами, поскольку компилятор не может гарантировать, что getData не изменяет foo.

...