Имеет ли смысл когда-либо использовать фундаментальный (не указатель) параметр const? - PullRequest
13 голосов
/ 16 марта 2010

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

void Foo(const int bar);

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

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

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

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

Edit:

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

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

Ответы [ 5 ]

26 голосов
/ 16 марта 2010

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

Есть много людей, которые скажут: «Вы должны написать такой код»:

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

вместо

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

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

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

Почему постоянные параметры могут быть полезны для кодера

Независимо от типа, «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) ;

имеют одинаковую подпись.

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

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

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

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

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

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

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

Однако меня укусил мой шаблон.

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

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

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

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

Примечание: у 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 ++ 1991-2006 , 5. Особенности языка: 1991-1998 , стр. 21.
http://www.stroustrup.com/hopl-almost-final.pdf

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

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

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

7 голосов
/ 16 марта 2010

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

Еще одна незначительная деталь: это относится и к ссылкам, и к указателям, хотя ...

4 голосов
/ 16 марта 2010

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

3 голосов
/ 16 марта 2010

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

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

3 голосов
/ 16 марта 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, но вы сигнализируете, что нужно быть внимательным и внимательным.

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

...