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

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

class MyClass {

public:
int getData() const;
};

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

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

Ответы

Ответ 1

Пожалуйста, см. эту отличную статью о корректности состязания Herb Sutter (секретарь комитета стандартов на С++ в течение 10 лет.)

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

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

Ответ 2

каждый раз, когда функция не изменяет объект, то есть каждый раз, когда функция "подходит" для 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

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

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

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

    /li >
  • Это заставит компилятор поймать непреднамеренные мутации (вызывая ошибку, если функция, отмеченная const, unintintionally вызывает функцию non-const или мутирует элемент).

Ответ 4

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

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

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

Ответ 5

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

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

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

Ответ 6

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

Ответ 7

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

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

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 и С++, который является копией. Поэтому подпрограмма может считывать значение переменной, переданной ей, но не изменять ее, потому что любые изменения, которые она делает, делаются только в копии и теряются при завершении подпрограммы

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

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

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

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

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

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

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

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

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

void Subroutine4(big_structure_type &Parameter1);

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

Чтобы решить эту проблему, 'const можно использовать в списке параметров, например

void Subroutine4(big_structure_type const &Parameter1);

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

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