Имеет ли смысл делать фундаментальный (не указатель) параметр константой?

Недавно у меня был обмен с другим разработчиком C ++ по поводу следующего использования const:

void Foo(const int bar);

Он считал, что использование const таким образом было хорошей практикой.

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

Не конец света, но определенно не то, что можно рекомендовать в качестве хорошей практики.

Мне любопытно, что думают по этому поводу другие.

Редактировать:

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

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


person Scott Smith    schedule 16.03.2010    source источник
comment
Я предполагаю, что политика должна быть такой же для создания константных локальных переменных. И я делаю это, но только когда думаю, что это улучшает читаемость кода. - Программы должны быть написаны для того, чтобы люди могли их читать, и только случайно, чтобы машины могли их выполнять. - Гарольд Абельсон   -  person    schedule 16.03.2010
comment
@Roger, когда добавление const не облегчает чтение и понимание кода? Разумеется, явное указание операций, которые вы можете выполнять с переменной, всегда способствует удобочитаемости, а не расплывчатости ... У вас есть пример, когда вы чувствуете, что добавление const ухудшает читаемость некоторого кода?   -  person Len Holgate    schedule 16.03.2010
comment
@Len: В основном короткий код: int f(int n) { return g(n)*2; }. Но помните, что вы должны стремиться к тому, чтобы все функции были относительно короткими, поскольку это также улучшает читаемость. Более реалистичный пример: bitbucket.org/kniht/scraps/src/tip /cpp/quicksort.hpp (сравните параметры и тело функции void quicksort(Iter const begin, Iter const end, Cmp lt, Distance const len) с void quicksort(Iter begin, Iter end, Cmp lt)).   -  person    schedule 16.03.2010
comment
@ Роджер, хорошо, я понимаю вашу точку зрения; но я бы, наверное, все же вставил const в ...   -  person Len Holgate    schedule 16.03.2010


Ответы (5)


Помните шаблон if(NULL == p)?

Многие люди скажут, что вы должны писать такой код:

if(NULL == myPointer) { /* etc. */ }

вместо

if(myPointer == NULL) { /* etc. */ }

Обоснование состоит в том, что первая версия защитит кодировщик от опечаток кода, таких как замена == на = (потому что запрещено присваивать значение постоянному значению).

Следующее можно рассматривать как расширение этого ограниченного if(NULL == p) шаблона:

Почему параметры const-ing могут быть полезны для кодировщика

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

Например, такой код покажет, когда компилятор может мне помочь:

void bar_const(const int & param) ;
void bar_non_const(int & param) ;

void foo(const int param)
{
   const int value = getValue() ;

   if(param == 25) { /* Etc. */ } // Ok
   if(value == 25) { /* Etc. */ } // Ok

   if(param = 25) { /* Etc. */ } // COMPILE ERROR
   if(value = 25) { /* Etc. */ } // COMPILE ERROR

   bar_const(param) ;  // Ok
   bar_const(value) ;  // Ok

   bar_non_const(param) ;  // COMPILE ERROR
   bar_non_const(value) ;  // COMPILE ERROR

   // Here, I expect to continue to use "param" and "value" with
   // their original values, so having some random code or error
   // change it would be a runtime error...
}

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

Почему это не важно для пользователя

Бывает, что:

void foo(const int param) ;

и:

void foo(int param) ;

иметь такую ​​же подпись.

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

Это объясняет, почему мои объявления функций пользователям опускают константу:

void bar(int param, const char * p) ;

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

void bar(const int param, const char * const p)
{
   // etc.
}

чтобы сделать мой код максимально надежным.

Почему в реальном мире это может сломаться

Однако меня укусил мой паттерн.

На каком-то неработающем компиляторе, который останется анонимным (имя которого начинается с Sol и заканчивается на aris CC), две подписи выше могут рассматриваться как разные (в зависимости от контекста), и, таким образом, ссылка времени выполнения возможно не сработает.

Поскольку проект был скомпилирован также на платформах Unix (Linux и Solaris), на этих платформах неопределенные символы оставались разрешенными при выполнении, что спровоцировало ошибку времени выполнения в середине выполнения процесса.

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

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

Примечание. У Sun Microsystems даже хватило смелости скрыть свое сломанное искажение с помощью шаблона это зло в любом случае, поэтому вы не должны использовать его. см. http://docs.oracle.com/cd/E19059-01/stud.9/817-6698/Ch1.Intro.html#71468

Последнее примечание

Следует отметить, что Бьярн Страуструп, похоже, был против того, чтобы рассматривать void foo(int) тот же прототип, что и void foo(const int):

Однако, на мой взгляд, не все принятые функции являются улучшением. Например, [...] правило, согласно которому void f (T) и void f (const T) обозначают одну и ту же функцию (предложенное Томом Пламом из соображений совместимости с C), [имеют] сомнительное отличие, поскольку за него проголосовали C ++ « через мой труп".

Источник: Бьярн Страуструп
Развитие языка в реальном мире и для него: C ++ 1991-2006, 5. Особенности языка: 1991–1998, стр. 21.
http://www.stroustrup.com/hopl-almost-final.pdf

Забавно учитывать, что Херб Саттер предлагает противоположную точку зрения:

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

Источник: Херб Саттер
Exceptional C ++, Item 43: Const-Correctness, p177-178.

person paercebal    schedule 16.03.2010
comment
Я никогда не знал, что вы можете изменить постоянство параметра без изменения сигнатуры функции. Несколько раз я считал полезным, чтобы параметр, не являющийся ссылкой, был явно константным, я определял ссылку на константу внутри тела функции. так что f (int i_) {const int & i = i_; } Может помочь, если вам снова понадобится использовать анонимный компилятор. - person Dennis Zickefoose; 16.03.2010
comment
@Dennis Zickefoose: ваша конструкция интересна (я думал о подобном случае, чтобы скрыть неконстантную локальную переменную, которую мне нужно было инициализировать, передав их как ссылки на другие функции), но она не удаляет исходную переменную (i_ остается доступным) , и кажется, что это слишком много кода, чтобы добавить полученное значение ... Для моего анонимного компилятора (кто написал Solaris CC?) я также просто добавил константу в объявление ... T_T ... - person paercebal; 16.03.2010
comment
Ааааааааааааааааааааааа! Меня снова укусила та же самая ошибка компилятора! Несмотря на тестовый сценарий, который я написал, некоторые функции, по-видимому, остались неопределенными (и необнаруженными) в сборке выпуска моего .SO, в то время как они были правильно определены в сборке отладки. Конечно, тестировщики обнаружили ошибку до того, как она могла быть выпущена, но в любом случае ... Спасибо вам, Sun Microsystems, за ваш сломанный компилятор! - person paercebal; 16.06.2010
comment
@paercebal: Херб Саттер не предлагает противоположной точки зрения в этом руководстве, он просто предлагает, как лучше всего справиться с ситуацией. - person agentsmith; 02.04.2020

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

Еще одна небольшая деталь: это применимо как к ссылкам, так и к указателям ...

person Jerry Coffin    schedule 16.03.2010
comment
+1, потому что я в основном согласен, но все, что стандарт говорит, заключается в том, что константа верхнего уровня, используемая в вопросе, является деталью реализации функции, а не частью интерфейса. Сказать, что стандарт соглашается с тем, что он бессмысленен, значит слишком много в него вкладывать. - person ; 16.03.2010
comment
@ Роджер: ну да. В конечном счете, стандарт (в основном) ограничивается изложением требований, а не комментариями к стилю (хотя, если вы будете искать самосожжение, вы увидите, что он иногда выходит за рамки этого, и в том, что официально является нормативной частью стандарта при этом. ! Когда-нибудь мне, возможно, придется проработать историю, чтобы выяснить, какой редактор добавил именно этот лакомый кусочек. :-) - person Jerry Coffin; 16.03.2010
comment
Самый известный программный лимерик. :) - person ; 16.03.2010
comment
он применяется к ссылкам так же, как и к указателям, хотя - не уверен, как вы хотите применить константу верхнего уровня к ссылкам. T &const недействителен, а константа, примененная к typedef ссылочного типа, бессмысленна и всегда игнорируется. - person Johannes Schaub - litb; 16.03.2010
comment
@Johannes: Честно говоря, я не уверен, о чем я думал, когда писал это, но это явно чушь ... - person Jerry Coffin; 16.03.2010

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

person Yuliy    schedule 16.03.2010
comment
Я согласен - за исключением того, что иногда мне кажется, что единственные ошибки, обнаруживаемые флагами const, - это те, где я забыл отметить что-то const. - person Steve314; 16.03.2010

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

Я сам голосую за "хорошую практику". Конечно, в наши дни я в значительной степени перешла на функциональные языки, так что ....


Обращаясь к комментарию ниже, рассмотрите этот исходный файл:

// test.c++

bool testSomething()
{
    return true;
}

int test1(int a)
{
    if (testSomething())
    {
        a += 5;
    }
    return a;
}

int test2(const int a)
{
    if (testSomething())
    {
        a += 5;
    }
    return a;
}

В test1 я не могу узнать, каким будет возвращаемое значение, не прочитав (потенциально значительного и / или запутанного) тела функции и без отслеживания (потенциально удаленного, значительного, запутанное и / или исходный код недоступен) тело функции testSomething. Кроме того, изменение a может быть результатом ужасной опечатки.

Та же самая опечатка в test2 приводит к следующему результату во время компиляции:

$ g++ test.c++
test.c++: In function ‘int test2(int)’:
test.c++:21: error: assignment of read-only parameter ‘a’

Если это была опечатка, меня поймали. Если это не опечатка, то лучше выбрать кодировку, IMO:

int test2(const int a)
{
    int b = a;
    if (testSomething())
    {
        b += 5;
    }
    return b;
}

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

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

person JUST MY correct OPINION    schedule 16.03.2010
comment
Я бы согласился, если бы вы сказали, является ли bar ссылкой или указателем и помечен ли он _2 _... - person greyfade; 16.03.2010
comment
Развернуто, чтобы объяснить не справочный, не указательный пример. - person JUST MY correct OPINION; 16.03.2010
comment
Зачем вам передавать что-то по значению const, если вы планируете скопировать это в значение, отличное от const, прежде чем фактически использовать? Вы по-прежнему не можете сказать, какое значение будет возвращено, не прочитав функцию [для чего и нужна документация], поэтому исходная константность вам совсем не поможет. - person Dennis Zickefoose; 16.03.2010
comment
Что ж, главное в этом - защита от смерти мозга. Опечатка для меня больше проблема, чем последняя. Однако копирование в неконстантное значение позволяет мне делать такие вещи, как, скажем, задействовать этот параметр в нескольких вычислениях, причем каждое вычисление точно знает, каким будет начальное состояние - нет места для ошибки, скажем, вставленная строка кода, случайно меняющая значение, когда оно не должно быть, и т. д. - person JUST MY correct OPINION; 16.03.2010
comment
Но вы представили возможность случайно использовать неправильное значение. Очевидно, что если вам нужно сохранить как исходное значение, так и модифицированную версию, вы должны создать вторую переменную. Но создание двух переменных на всякий случай сделает ваш код сложнее, а не проще. - person Dennis Zickefoose; 16.03.2010
comment
Деннис: рискуя начать дискуссию о неизменяемых и изменяемых значениях, основная причина для этого заключается в том, что исходные и модифицированные значения, вероятно, имеют связанную с ними разную семантику. - person Yuliy; 18.03.2010

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

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

person Len Holgate    schedule 16.03.2010