рекомендуется ли добавлять const в конце функций-членов - где это уместно?

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

class MyClass {

public:
int getData() const;
};

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

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


person Edoz    schedule 29.04.2011    source источник
comment
Ставьте const везде, кроме тех, где нельзя.   -  person Etienne de Martel    schedule 29.04.2011
comment
Эффективный C++ имеет очень хорошую главу именно по этому вопросу.   -  person Xeo    schedule 29.04.2011


Ответы (7)


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

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

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

person Neil G    schedule 29.04.2011
comment
+1, в качестве ссылки: stackoverflow.com/questions/522828/ - person Matthieu M.; 29.04.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
         }
};
person Nawaz    schedule 29.04.2011
comment
Даже если он изменяет одну или несколько внутренних переменных один или два раза, даже тогда я обычно делаю его константной функцией-членом. И эти переменные объявляются с помощью ключевого слова mutable. Кажется, это совсем не так. Есть несколько особых случаев, когда вы должны пометить переменные как изменяемые. Пример, который вы привели, кажется хорошим, но то, как вы его изложили, подразумевает, что количество раз, когда вы меняете переменные-члены, имеет значение при принятии решения о маркировке функций константными или изменяемыми переменными. - person sleeparrow; 02.02.2014
comment
@Adamantite: ключевое слово internal, когда я сказал: Даже если оно изменяет одну или несколько внутренних переменных один или два раза, даже тогда я обычно делаю его константной функцией-членом. . И эти переменные объявляются с ключевым словом mutable. То есть предполагается, что внутренние переменные находятся в ненаблюдаемых состояниях! - person Nawaz; 03.02.2014
comment
Аааа, теперь вижу. Имеет смысл. - person sleeparrow; 03.02.2014

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

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

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

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

person Michael Aaron Safyan    schedule 29.04.2011
comment
Нет, обычно это никак не влияет на скорость (в конце концов, константная функция потенциально может изменить глобальное состояние, даже если она не изменяет текущий объект, поэтому консервативные оптимизации не могут ее использовать). Однако при агрессивной оптимизации компилятор может использовать это как подсказку. - person Michael Aaron Safyan; 29.04.2011

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

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

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

person BjoernD    schedule 29.04.2011

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

Например, если функция, которая возвращает фиксированную строку «Некоторый текст», написана как

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

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

Function1()[1]=’a’;

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

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

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

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

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

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

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

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

Добавление «&» к имени параметра в C++ (что было очень запутанным выбором символа, потому что «&» перед переменными в других местах C генерирует указатели!) как приводит к тому, что сама фактическая переменная, а не копия, использоваться в качестве параметра в подпрограмме и, следовательно, может быть записан для передачи данных обратно из подпрограммы. Следовательно

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);

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

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

person Community    schedule 29.04.2011

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

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

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

person Ben    schedule 29.04.2011

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

person Tony Delroy    schedule 29.04.2011