Как далеко вы зашли с const? Вы просто делаете функции const, когда это необходимо, или вы делаете все возможное и используете его везде? Например, представьте простой мутатор, который принимает единственный логический параметр:
void SetValue(const bool b) { my_val_ = b; }
Этот const действительно полезен? Лично я предпочитаю широко использовать его, включая параметры, но в этом случае мне интересно, стоит ли оно того?
Я также был удивлен, узнав, что вы можете опустить const из параметров в объявлении функции, но можете включить его в определение функции, например:
.h файл
void func(int n, long l);
.cpp файл
void func(const int n, const long l)
Есть причина для этого? Мне это кажется немного необычным.
Я согласен. :-) (С вопросом, а не последним комментарием!) Если значение не должно изменяться в теле функции, это может помочь остановить глупые ошибки == или =, вы никогда не должны помещать const в оба, (если он передается по значению, иначе вы должны) Это недостаточно серьезно, чтобы спорить об этом!
@selwyn: Даже если вы передадите в функцию const int, она будет скопирована (поскольку это не ссылка), поэтому константа не имеет значения.
Те же дебаты происходят и в этом вопросе: stackoverflow.com/questions/1554750/…
Я понимаю, что этому посту уже несколько лет, но, как новый программист, я задавался этим вопросом и наткнулся на этот разговор. На мой взгляд, если функция не должна изменять значение, будь то ссылка или копия значения / объекта, она должна быть константой. Это безопаснее, самодокументируется и удобнее для отладки. Даже для самой простой функции, в которой есть один оператор, я все равно использую const.
Теперь с семантикой перемещения, имеющей const, может снизиться производительность





Причина в том, что const для параметра применяется только локально внутри функции, поскольку он работает с копией данных. Это означает, что сигнатура функции в любом случае такая же. Хотя, наверное, делать это часто - плохой стиль.
Лично я не использую const, за исключением параметров ссылки и указателя. Для скопированных объектов это не имеет значения, хотя может быть безопаснее, поскольку сигнализирует о намерении внутри функции. Это действительно призыв к суждению. Я обычно использую const_iterator, когда зацикливаюсь на чем-то, и я не собираюсь его изменять, поэтому я предполагаю, что каждому свое, если строго соблюдается правильность const для ссылочных типов.
Я не могу согласиться с частью «плохого стиля». Удаление const из прототипов функций имеет то преимущество, что вам не нужно изменять файл заголовка, если вы решите удалить const из части реализации позже.
«Лично я не использую const, за исключением параметров ссылки и указателя». Может быть, вам следует пояснить, что «Я стараюсь не использовать лишние квалификаторы в объявлениях функций, а использую const там, где это имеет полезное значение».
Я не согласен с этим ответом. Я наклоняюсь в другую сторону и по возможности отмечаю параметры const; это более выразительно. Когда я читаю чужой код, я использую такие маленькие индикаторы, чтобы судить о том, сколько внимания они уделяют написанию своего кода наряду с такими вещами, как магические числа, комментарии, правильное использование указателей и т. д.
int getDouble(int a){ ++a; return 2*a; } Попробуйте это. Конечно, ++a здесь не при чем, но может можно найти там в длинной функции, написанной более чем одним программистом в течение длительного периода времени. Я настоятельно рекомендую написать int getDouble( const int a ){ //... }, который будет генерировать ошибку компиляции при обнаружении ++a;.
Все зависит от того, кому какая информация нужна. Вы предоставляете параметр по стоимости, чтобы вызывающий не нужно ничего знать определял, что вы делаете (внутренне) с ним. Так что напишите class Foo { int multiply(int a, int b) const; } в шапке. В вашей реализации ты заботишься вы можете пообещать не изменять a и b, поэтому int Foo::multiply(const int a, const int b) const { } имеет здесь смысл. (Примечание: и вызывающая, и реализация заботятся о том, чтобы функция не изменяла свой объект Foo, следовательно, константу в конце своего объявления)
Я также не думаю, что этот «ответ» справедлив, если я считаю, что это плохой стиль, что это не имеет особого значения. В вызове суждения есть некоторая практическая истина, но все аргументы для const в общем случае переменной применимы, и это проблема / недосмотр языка, которая не рассматривается по-разному для объявления. Я бы сказал, что меня не устраивают объяснения «плохой стороны», которые я вижу в других местах (которые имеют другие побочные эффекты). Для stackoverflow было плохо выбрать это в качестве ответа, не потому, что он не может быть частью более крупного ответа, а потому, что он неполон для сложной проблемы C++.
@ MichałGórny просто представьте первую строку вашей функции как void func (unsigned char const * const buffer, size_t const length); это выглядит неуклюже. Но вместо этого, если мы напишем как void func (unsigned char const * buffer, size_t length); , это выглядит намного лучше
Стоит отметить, что Страуструп (по крайней мере, в TC++ PL 4-е изд.) использует const только для параметров указателя и ссылки. Его главный комментарий по поводу передачи по значению const заключается в том, что нет опасности изменения вызывающего контекста вне зависимости от того, используется const или нет (см. Стр. 308). Одним из аргументов против const по значению является то, что беспокойство о нем требует определенного количества времени и энергии, которые лучше потратить в более значимых местах - ваши функции должны быть краткими и достаточно ясными, чтобы вы могли легко отслеживать, что происходит с параметры в любом случае.
Я использую const для параметров функции, которые являются ссылками (или указателями), которые являются только данными [в] и не будут изменены функцией. Это означает, что когда цель использования ссылки - избежать копирования данных и не разрешить изменение переданного параметра.
Включение const в логический параметр b в вашем примере только накладывает ограничение на реализацию и не влияет на интерфейс класса (хотя обычно рекомендуется не изменять параметры).
Сигнатура функции для
void foo(int a);
и
void foo(const int a);
то же самое, что объясняет ваши .c и .h
Асаф
В случае, о котором вы упомянули, это не влияет на вызывающих ваш API, поэтому это обычно не делается (и не обязательно в заголовке). Это влияет только на реализацию вашей функции.
Это не особенно плохо, но преимущества не так уж велики, учитывая, что это не влияет на ваш API и добавляет набор текста, поэтому обычно этого не делается.
Я бы не стал добавлять константу к таким параметрам - все уже знают, что логическое значение (в отличие от логического &) является константой, поэтому его добавление заставит людей подумать: "Подождите, что?" или даже то, что вы передаете параметр по ссылке.
иногда вы хотите передать объект по ссылке (по соображениям производительности), но не изменять его, поэтому const является обязательным. Сохранение всех таких параметров - даже bools - const было бы хорошей практикой, что упростило бы чтение вашего кода.
при использовании const нужно помнить, что гораздо проще сделать вещи const с самого начала, чем пытаться вставить их позже.
Используйте const, если хотите, чтобы что-то не изменилось - это дополнительная подсказка, которая описывает, что делает ваша функция и чего ожидать. Я видел много C API, которые могли бы работать с некоторыми из них, особенно те, которые принимают c-строки!
Я был бы более склонен опустить ключевое слово const в файле cpp, чем в заголовке, но, поскольку я стараюсь вырезать + вставить их, они будут храниться в обоих местах. Я понятия не имею, почему компилятор позволяет это, я думаю, что это компилятор. Лучше всего поместить ключевое слово const в оба файла.
Я вообще этого не понимаю. Почему Не могли бы вы опустить его в файле cpp (определение функции)? Вот где это на самом деле что-то значит и может обнаруживать ошибки. Почему как вы думаете, лучше всего использовать const в обоих местах? В заголовочном файле (объявлении функции) это ничего не значит и загромождает API. Может быть, есть небольшая ценность в том, чтобы объявления decl и defn выглядели одинаково, но мне кажется, что это действительно незначительное преимущество по сравнению с проблемой загромождения API.
@DonHatch 8 лет спустя, вау. В любом случае, как сказал OP: «Я также был удивлен, узнав, что вы можете опустить const из параметров в объявлении функции, но можете включить его в определение функции».
Ах, крутой. С одной стороны, объявление - это контракт, и на самом деле нет смысла передавать аргумент const по значению. С другой стороны, если вы посмотрите на реализацию функции, вы дадите компилятору больше шансов на оптимизацию, если объявите константу аргумента.
Все константы в ваших примерах не имеют цели. C++ по умолчанию является передачей по значению, поэтому функция получает копии этих целых чисел и логических значений. Даже если функция изменяет их, копия вызывающего не затрагивается.
Поэтому я бы избегал лишних констант, потому что
На самом деле нет причин делать параметр значения «константой», поскольку функция в любом случае может изменять только копию переменной.
Причина использования «const» в том, что вы передаете что-то большее (например, структуру с большим количеством членов) по ссылке, и в этом случае это гарантирует, что функция не может ее изменить; или, скорее, компилятор пожалуется, если вы попытаетесь изменить его обычным способом. Это предотвращает его случайное изменение.
Я использую const, если могу. Константа для параметров означает, что они не должны изменять свое значение. Это особенно ценно при передаче по ссылке. const для функции объявляет, что функция не должна изменять члены классов.
Я не использую const для параметра, передающего значение. Вызывающему не важно, измените ли вы параметр или нет, это деталь реализации.
Что действительно важно, так это помечать методы как константы, если они не изменяют свой экземпляр. Делайте это на ходу, потому что в противном случае вы можете получить либо большое количество const_cast <>, либо вы можете обнаружить, что маркировка метода const требует изменения большого количества кода, потому что он вызывает другие методы, которые должны были быть помечены как const.
Я также обычно помечаю локальные переменные как const, если мне не нужно их изменять. Я считаю, что это упрощает понимание кода, облегчая идентификацию «движущихся частей».
Параметр Const полезен только тогда, когда параметр передается по ссылке, то есть по ссылке или указателю. Когда компилятор видит параметр const, он проверяет, что переменная, используемая в параметре, не изменяется в теле функции. Зачем кому-то нужно делать параметр по значению постоянным? :-)
По многим причинам. Создание параметра по значению const четко указывает: «Мне не нужно изменять это, поэтому я заявляю об этом. Если я попытаюсь изменить его позже, выдаст мне ошибку времени компиляции, чтобы я мог исправить свою ошибку или снять отметку const ». Так что это вопрос как гигиены кода, так и безопасности. Несмотря на все, что требуется для добавления в файлы реализации, люди должны делать это как чистый рефлекс, ИМО.
Если параметр передается по значению (и не является ссылкой), обычно нет большой разницы, объявлен параметр как константный или нет (если он не содержит ссылочный член - это не проблема для встроенных типов). Если параметр является ссылкой или указателем, обычно лучше защищать указанную / указанную память, а не сам указатель (я думаю, вы не можете сделать саму ссылку константой, но это не имеет большого значения, поскольку вы не можете изменить рефери) . Кажется хорошей идеей защитить все, что можно, как const. Вы можете опустить его, не опасаясь ошибки, если параметры являются просто POD (включая встроенные типы) и нет шансов, что они изменятся дальше по дороге (например, в вашем примере параметр bool).
Я не знал о разнице в объявлении файлов .h / .cpp, но в этом есть смысл. На уровне машинного кода ничего не является «константой», поэтому, если вы объявляете функцию (в .h) как неконстантную, код будет таким же, как если бы вы объявили ее как константу (не считая оптимизаций). Однако это поможет вам привлечь компилятор, чтобы вы не изменили значение переменной внутри реализации функции (.ccp). Это может пригодиться в случае, если вы наследуете интерфейс, который позволяет изменять, но вам не нужно изменять параметр для достижения требуемой функциональности.
Я стараюсь использовать const везде, где это возможно. (Или другое подходящее ключевое слово для целевого языка.) Я делаю это исключительно потому, что это позволяет компилятору делать дополнительные оптимизации, которые он не смог бы сделать в противном случае. Поскольку я понятия не имею, что это за оптимизации, я всегда делаю это, даже когда это кажется глупым.
Насколько я знаю, компилятор вполне может увидеть параметр значения const и сказать: «Эй, эта функция все равно не изменяет его, поэтому я могу передать его по ссылке и сохранить несколько тактов». Я не думаю, что он когда-либо мог бы сделать такое, поскольку он меняет сигнатуру функции, но в этом есть смысл. Может быть, он выполняет какие-то другие манипуляции со стеком или что-то в этом роде ... Дело в том, что я не знаю, но я знаю, что попытки быть умнее компилятора только приводят меня к стыду.
У C++ есть дополнительный багаж, связанный с идеей константной корректности, поэтому он становится еще более важным.
Хотя в некоторых случаях это может помочь, я подозреваю, что возможность содействия оптимизации сильно преувеличена как преимущество const. Скорее, это вопрос формулировки намерения в реализации и улавливания мыслей позже (случайное увеличение неправильной локальной переменной, потому что это был не const). Параллельно я бы также добавил, что компиляторы очень приветствуются для изменения сигнатуры функций в том смысле, что функции могут быть встроены, а после встраивания можно изменить весь способ их работы; добавление или удаление ссылок, создание литералов «переменных» и т. д. все в рамках правила «как если бы»
Следующие две строки функционально эквивалентны:
int foo (int a);
int foo (const int a);
Очевидно, вы не сможете модифицировать a в теле foo, если он определен вторым способом, но снаружи нет никакой разницы.
const действительно пригодится с параметрами ссылки или указателя:
int foo (const BigStruct &a);
int foo (const BigStruct *a);
Это говорит о том, что foo может принимать большой параметр, возможно, структуру данных размером в гигабайты, без ее копирования. Кроме того, он говорит вызывающему: «Foo не будет * изменять содержимое этого параметра». Передача константной ссылки также позволяет компилятору принимать определенные решения о производительности.
*: Если он не откажется от константности, но это другой пост.
Вопрос не в этом; конечно, для аргументов, на которые есть ссылка или указывается, рекомендуется использовать const (если указанное или указанное значение не изменяется). Обратите внимание, что в вашем примере указателя константой является не параметр; это то, на что указывает параметр.
> Передача константной ссылки также позволяет компилятору принимать определенные решения о производительности. классическая ошибка - компилятор должен сам определять константу, ключевое слово const в этом не помогает благодаря псевдониму указателя и const_cast
const бессмысленна, когда аргумент передается по значению, поскольку вы не будете изменять объект вызывающего.
При передаче по ссылке предпочтительнее использовать const, если только целью функции не является изменение переданного значения.
Наконец, функция, которая не изменяет текущий объект (this), может и, вероятно, должна быть объявлена как const. Пример ниже:
int SomeClass::GetValue() const {return m_internalValue;}
Это обещание не изменять объект, к которому применяется этот вызов. Другими словами, вы можете вызвать:
const SomeClass* pSomeClass;
pSomeClass->GetValue();
Если бы функция не была константной, это привело бы к предупреждению компилятора.
constis pointless when the argument is passed by value since you will not be modifying the caller's object.
Неправильный.
Речь идет о самодокументировании вашего кода и ваших предположений.
Если над вашим кодом работает много людей, а ваши функции нетривиальны, вам следует пометить const все, что вы можете. При написании кода промышленного уровня вы всегда должны предполагать, что ваши коллеги - психопаты, пытающиеся вывести вас любым возможным способом (тем более, что в будущем это часто вы сами).
Кроме того, как кто-то упоминал ранее, мог бы помогает компилятору немного оптимизировать работу (хотя это далеко не всегда).
Полностью согласен. Все дело в общении с людьми и ограничении того, что можно сделать с переменной, тем, что должно быть сделано.
Я проголосовал против. Я думаю, вы разбавляете то, что пытаетесь указать, с помощью const, когда применяете его к простым аргументам передачи по значению.
Я проголосовал за это. Объявление параметра const добавляет к параметру семантическую информацию. Они подчеркивают то, что задумал первоначальный автор кода, и это поможет поддерживать код с течением времени.
@tonylo: вы неправильно поняли. Речь идет о том, чтобы пометить локальную переменную как константу внутри блока кода (который является функцией). Я бы сделал то же самое для любой локальной переменной. Он ортогонален константно-корректному API, что действительно также важно.
Не совсем, я думаю, вы просто переоцениваете полезность const. Я думаю, [полезность] появляется тогда, когда важно определить право собственности на объект. Взгляните на API POSIX, откройте (2), например, путь (указатель) стоит сделать константой, для флагов и режимов это не имеет значения.
И он может отлавливать ошибки внутри функции - если вы знаете, что параметр не должен изменяться, то объявление его как const означает, что компилятор сообщит вам, если вы случайно измените его.
@tonylo, я к этому вопросу отношусь нейтрально. Если люди вводят в параметры const, хорошо. Но я не делаю этого в своем коде. Но упоминание руководств open и т. д. На самом деле не является аргументом. Потому что const не будет частью интерфейса. Так что показывать const на страницах руководства было бы неправильно.
@Partial, я согласен с вами в том, что никогда не ошибаюсь писать это. Но также не плохо не всегда его везде наклеивать. Бывают случаи, когда я считаю, что выгода не оправдывает ее. Это личное мнение: вы никогда не увидите, чтобы я голосовал против любого такого ответа только из-за моего личного мнения, и вы никогда не прочитаете, что я пишу, что помещать const где-либо неправильно.
Но это ограничивает ваш выбор алгоритмов (или заставляет вас делать ненужные копии) - если я назову ваш sin (x), мне все равно, должен ли ваш алгоритм sin () изменить свою копию X, пока он оценивает ответ.
Я согласен с вами, что const следует использовать как можно чаще, но константа не должна быть в файле заголовка, потому что пользователя интерфейса не интересует, что делается внутри тела метода / функции с параметром, переданным через ценить.
«Reductio ad absurdum» - ответ призывает использовать «const» всякий раз, когда это возможно. Это просто приводит к загромождению API, которые труднее читать, и ограничивает алгоритмы. void bytecopy(char * CONST dest, const char * CONST source, CONST int count) { while(count--) { *dest++=*source++; } } не будет работать, и три из четырех констант (заглавные) не нужны.
«Вы всегда должны предполагать, что ваши коллеги - психопаты, пытающиеся достать вас любым возможным способом», - заставили меня громко смеяться на работе. +1 за это.
Если ваши коллеги - психопаты, ожидайте, что они напишут struct Foo { void bar(const string); }; void Foo::bar(string s) { s = "I modified a string which was supposed to be const"; } «Так много для самодокументирования».
-1 за то, что сказал Окталист, есть психопаты, а есть постоянные психопаты. Часто это один и тот же шизофреник. Если вы когда-либо отбрасываете const только один раз в любом месте кода, корректного для констант, все предположения о самодокументирующемся коде разваливаются, поскольку вы больше не можете доверять API.
Для меня есть еще одна причина написать здесь const. Я делаю это по веским причинам. Я привык делать параметры по умолчанию константными, так зачем останавливаться на типах POD? (только мое личное мнение)
Я думаю, что использование const с параметрами, не являющимися ссылками / указателями, совпадает с окружающим оператором return с круглыми скобками.
Искренне согласен! Настоящий разработчик API использует const.
Чего не хватает в этом обсуждении, так это различия между контрактом интерфейса и реализацией. Добавление константы к параметру по значению в объявлении метода не меняет семантику интерфейса и, следовательно, не может ничего сигнализировать клиенту - константа не является частью контракта. Было бы полезно включить его в определение, сигнализируя будущим разработчикам, что он не предназначен для изменения в реализации. Можно использовать const в объявлении без const в определении. по значению const в интерфейсе лишний беспорядок.
Позвольте мне не согласиться с вашим ответом здесь @rlerallut (хотя ему сейчас 10 лет). Константа в заголовке для аргументов по значению не дает значения или даже отрицательного значения. С одной стороны, это не выполняется компилятором, поэтому разработчику ничто не мешает удалить «const» в реализации (а затем забыть обновить заголовок). С другой стороны, пользователю заголовка не выгодно знать, что функция (предположительно - см. Ранее) использует значение константным способом. В лучшем случае это шум в заголовке, в худшем - обещания, которые исполнитель может легко нарушить.
Если бы только const был параметром по умолчанию, и вам пришлось бы изо всех сил, чтобы сделать параметр изменяемым, добавив к нему префикс ключевого слова. Существующий синтаксис C++ кажется похожим на то, как в TypeScript вам нужно написать дополнительный синтаксис для использования строгой типизации!
Иногда (слишком часто!) Мне приходится распутывать чужой код C++. И все мы знаем, что код чей-то еще C++ - это полный беспорядок почти по определению :) Итак, первое, что я делаю для расшифровки локального потока данных, помещаю const в каждое определение переменной, пока компилятор не начнет лаять. Это также означает аргументы-значения, определяющие константу, потому что это просто причудливые локальные переменные, инициализированные вызывающей стороной.
Ах, я бы хотел, чтобы переменные были const по умолчанию, а изменчивый требовалось для неконстантных переменных :)
«Я бы хотел, чтобы переменные по умолчанию были константными» - оксюморон ?? 8-) Серьезно, как «конструирование» всего помогает распутать код? Если исходный автор изменил предположительно постоянный аргумент, как узнать, что переменная должна быть константой? Более того, подавляющее большинство (не аргументов) переменных предназначены быть ... переменными. Итак, компилятор должен сломаться очень скоро после того, как вы начали процесс, не так ли?
@ysap, 1. Максимальное обозначение const позволяет мне видеть, какие части движутся, а какие нет. По моему опыту, многие местные жители де-факто постоянны, а не наоборот. 2. «Постоянная переменная» / «Неизменяемая переменная» может звучать как оксюморон, но это стандартная практика в функциональных языках, а также в некоторых нефункциональных языках; см. Rust, например: doc.rust-lang.org/book/variable-bindings.html
Также теперь стандартен в некоторых случаях в C++; например, лямбда [x](){return ++x;} - это ошибка; см. здесь
Переменные по умолчанию "const" в Ржавчина :)
Переменные не обязательно должны быть присваиваемыми, чтобы они могли изменяться; значение, которым они инициализируются, также может изменяться во время выполнения.
Когда я зарабатывал на жизнь кодированием C++, я делал все, что мог. Использование const - отличный способ помочь компилятору вам в этом помочь. Например, создание возвращаемых значений вашего метода может спасти вас от таких опечаток, как:
foo() = 42
когда вы имели в виду:
foo() == 42
Если foo () определен для возврата неконстантной ссылки:
int& foo() { /* ... */ }
Компилятор с радостью позволит вам присвоить значение анонимному временному объекту, возвращаемому вызовом функции. Делаем это const:
const int& foo() { /* ... */ }
Устраняет эту возможность.
С каким компилятором это работало? GCC выдает ошибку при попытке скомпилировать foo() = 42: error: lvalue требуется как левый операнд присваивания
Это просто неверно. foo () = 42 то же самое, что 2 = 3, т.е. ошибка компилятора. А возвращать константу совершенно бессмысленно. Он ничего не делает для встроенного типа.
Я столкнулся с таким использованием const и могу вам сказать, что в конечном итоге это приносит больше хлопот, чем пользы. Подсказка: const int foo() относится к другому типу, чем int foo(), что создает большие проблемы, если вы используете такие вещи, как указатели на функции, системы сигналов / слотов или boost :: bind.
Я исправил код, чтобы включить возвращаемое значение ссылки.
Разве const int& foo() не то же самое, что int foo(), из-за оптимизации возвращаемого значения?
Обозначение параметров значения «const» - вещь определенно субъективная.
Однако на самом деле я предпочитаю отмечать параметры значения как const, как в вашем примере.
void func(const int n, const long l) { /* ... */ }
Для меня значение четко указывает на то, что значения параметров функции никогда не меняются функцией. В начале они будут иметь то же значение, что и в конце. Для меня это часть очень функционального стиля программирования.
Для короткой функции, возможно, будет пустой тратой времени / пространства иметь там 'const', поскольку обычно довольно очевидно, что аргументы не изменяются функцией.
Однако для более крупной функции это форма документации по реализации, и она обеспечивается компилятором.
Я могу быть уверен, что если я произведу какие-то вычисления с n и l, я смогу реорганизовать / переместить это вычисление, не опасаясь получить другой результат, потому что я пропустил место, где одно или оба были изменены.
Поскольку это деталь реализации, вам не нужно объявлять параметры значения const в заголовке, точно так же, как вам не нужно объявлять параметры функции с теми же именами, что и в реализации.
Поскольку параметры передаются по значению, не имеет значения, указываете вы const или нет с точки зрения вызывающей функции. В принципе, нет никакого смысла объявлять передаваемые по значению параметры как const.
const должно было быть значением по умолчанию в C++. Так :
int i = 5 ; // i is a constant
var int i = 5 ; // i is a real variable
Совместимость с C слишком важна, по крайней мере для людей, которые разрабатывают C++, чтобы даже подумать об этом.
Интересно, я никогда об этом не думал.
Точно так же unsigned должен был быть по умолчанию в C++. Как это: int i = 5; // i is unsigned и signed int i = 5; // i is signed.
Я говорю const ваши параметры значения.
Рассмотрим эту глючную функцию:
bool isZero(int number)
{
if (number = 0) // whoops, should be number == 0
return true;
else
return false;
}
Если числовой параметр был const, компилятор остановился бы и предупредил нас об ошибке.
другой способ - использовать if (0 == number) ... else ...;
@ ChrisHuang-Leaver Ужасно, это не так, если вы говорите, как Йода, вы делаете: stackoverflow.com/a/2430307/210916
GCC / Clang -Wall дает вам -Wparentheses, который требует, чтобы вы сделали это «if ((number = 0))», если это действительно то, что вы намеревались сделать. Что хорошо работает как замена Йоде.
На эту тему есть хорошее обсуждение в старых статьях «Гуру недели» на comp.lang.C++. Moderated здесь.
Соответствующая статья GOTW доступна на веб-сайте Херба Саттера здесь.
Херб Саттер - действительно умный парень :-) Определенно стоит прочитать, и я согласен со ВСЕМИ его пунктами.
Хорошая статья, но я не согласен с ним насчет аргументов. Я также делаю их константами, потому что они похожи на переменные, и я никогда не хочу, чтобы кто-либо вносил какие-либо изменения в мои аргументы.
По оптимизации компилятора: http://www.gotw.ca/gotw/081.htm
Лишние лишние константы - это плохо с точки зрения API:
Добавление лишних констант в код для параметров внутреннего типа, передаваемых значением загромождает ваш API, без каких-либо значимых обещаний вызывающему абоненту или пользователю API (это только затрудняет реализацию).
Слишком много 'const' в API, когда оно не нужно, похоже на "воющий волк", в конечном итоге люди начнут игнорировать 'const', потому что он повсюду и большую часть времени ничего не значит.
Аргумент «reductio ad absurdum» для дополнительных констант в API хорош для этих первых двух пунктов: если подходят больше константных параметров, то каждый аргумент, который может содержать константу, ДОЛЖЕН иметь константу. Фактически, если бы это было действительно так хорошо, вы бы хотели, чтобы const была параметром по умолчанию и имела ключевое слово вроде «mutable» только тогда, когда вы хотите изменить параметр.
Итак, давайте попробуем ввести const, где только сможем:
void mungerum(char * buffer, const char * mask, int count);
void mungerum(char * const buffer, const char * const mask, const int count);
Рассмотрим строку кода выше. Мало того, что объявление более загромождено, длиннее и сложнее для чтения, три из четырех ключевых слов const могут быть безопасно проигнорированы пользователем API. Однако дополнительное использование 'const' сделало вторую строку потенциально ОПАСНЫЙ!
Почему?
Быстрое неверное прочтение первого параметра char * const buffer может заставить вас подумать, что он не будет изменять память в буфере данных, который передается - однако это неверно! Излишняя const может привести к опасным и неверным предположениям о вашем API. при быстром сканировании или неправильном чтении.
Лишние константы тоже плохо с точки зрения реализации кода:
#if FLEXIBLE_IMPLEMENTATION
#define SUPERFLUOUS_CONST
#else
#define SUPERFLUOUS_CONST const
#endif
void bytecopy(char * SUPERFLUOUS_CONST dest,
const char *source, SUPERFLUOUS_CONST int count);
Если FLEXIBLE_IMPLEMENTATION неверно, то API «обещает» не реализовывать функцию первым способом, описанным ниже.
void bytecopy(char * SUPERFLUOUS_CONST dest,
const char *source, SUPERFLUOUS_CONST int count)
{
// Will break if !FLEXIBLE_IMPLEMENTATION
while(count--)
{
*dest++=*source++;
}
}
void bytecopy(char * SUPERFLUOUS_CONST dest,
const char *source, SUPERFLUOUS_CONST int count)
{
for(int i=0;i<count;i++)
{
dest[i]=source[i];
}
}
Это очень глупое обещание. Почему вы должны давать обещание, которое не приносит никакой пользы вашему вызывающему, а только ограничивает вашу реализацию?
Обе эти функции являются вполне допустимыми реализациями одной и той же функции, поэтому все, что вы сделали, это без надобности связали одну руку за спиной.
Кроме того, это очень поверхностное обещание, которое легко (и юридически обойти).
inline void bytecopyWrapped(char * dest,
const char *source, int count)
{
while(count--)
{
*dest++=*source++;
}
}
void bytecopy(char * SUPERFLUOUS_CONST dest,
const char *source,SUPERFLUOUS_CONST int count)
{
bytecopyWrapped(dest, source, count);
}
Послушайте, я все равно реализовал это таким образом, хотя обещал не делать этого - просто используя функцию-оболочку. Это похоже на то, когда плохой парень обещает никого не убивать в фильме и вместо этого приказывает своему приспешнику убить его.
Эти лишние константы стоят не больше, чем обещание плохого парня из фильма.
Но способность лгать становится еще хуже:
Я был проинформирован о том, что вы можете не соответствовать константе в заголовке (объявлении) и коде (определении), используя ложную константу. Сторонники const-happy утверждают, что это хорошо, поскольку позволяет указывать константу только в определении.
// Example of const only in definition, not declaration
struct foo { void test(int *pi); };
void foo::test(int * const pi) { }
Однако верно и обратное ... вы можете поместить ложную константу только в объявление и игнорировать ее в определении. Это только делает лишнюю const в API более ужасной вещью и ужасной ложью - см. Этот пример:
struct foo
{
void test(int * const pi);
};
void foo::test(int *pi) // Look, the const in the definition is so superfluous I can ignore it here
{
pi++; // I promised in my definition I wouldn't modify this
}
Все лишнее const на самом деле делает код разработчика менее читаемым, заставляя его использовать другую локальную копию или функцию-оболочку, когда он хочет изменить переменную или передать переменную по неконстантной ссылке.
Взгляните на этот пример. Что более читабельно? Очевидно ли, что единственная причина использования дополнительной переменной во второй функции заключается в том, что какой-то дизайнер API добавил лишнюю константу?
struct llist
{
llist * next;
};
void walkllist(llist *plist)
{
llist *pnext;
while(plist)
{
pnext=plist->next;
walk(plist);
plist=pnext; // This line wouldn't compile if plist was const
}
}
void walkllist(llist * SUPERFLUOUS_CONST plist)
{
llist * pnotconst=plist;
llist *pnext;
while(pnotconst)
{
pnext=pnotconst->next;
walk(pnotconst);
pnotconst=pnext;
}
}
Надеюсь, мы кое-что узнали здесь. Излишняя константа - это бельмо на глазу, мешающее API, раздражающее ворчание, поверхностное и бессмысленное обещание, ненужное препятствие и иногда приводящее к очень опасным ошибкам.
Почему отрицательные голоса? Будет гораздо полезнее, если вы оставите краткий комментарий к голосованию против.
Весь смысл использования аргумента const заключается в том, чтобы пометить строку не удалось (plist = pnext). Сохранение неизменности аргумента функции - разумная мера безопасности. Я согласен с вашей точкой зрения, что они плохи в объявлениях функций (поскольку они избыточны), но они могут служить своим целям в блоке реализации.
Нет причин загромождать ваш API, чтобы раскрыть детали внутренней реализации. Достаточно просто, чтобы ваша реализация принимала параметры T parm_foo и сразу же назначала const &T foo=parm_foo, а затем использовала foo в функции - вы можете обеспечить постоянство параметров без ненужного беспорядка API для конечных пользователей.
@Adisak Я не вижу ничего плохого в вашем ответе как таковом, но из ваших комментариев кажется, что вы упускаете важный момент. Определение / реализация функции - это часть API нет, которая представляет собой только функцию декларация. Как вы уже сказали, объявление функций с параметрами const бессмысленно и добавляет беспорядка. Однако пользователям API может никогда не понадобиться его реализация. Тем временем разработчик может решить ограничить константой некоторые параметры в определении функции ТОЛЬКО для ясности, что совершенно нормально.
@ jw013 Язык не допускает реализации / определения функций, которые отличаются от деклараций. Следовательно, они ИМЕЮТ должны быть одинаковыми. «Хороший» API должен открывать только то, что фактически составляет действительный «контракт» с пользователем, и не должен быть завален лишней чепухой, не влияющей на пользователя API. Я считаю, что хороший / чистый API лучше некоторых глупых поддельных ограничений реализации, которые легко обойти.
@Adisak Я не так хорошо знаком с педантичными деталями C++, как с C, но C99 определенно позволяет это (имея const в определении, но не в объявлении прототипа). Я никогда не соглашался с вами в том, что ясность API имеет первостепенное значение и не должна загромождаться деталями реализации.
@ jw013 C++ позволяет создавать функции с одинаковыми именами и разными подписями. Это называется перегрузкой функций. Он реализуется компилятором, декорирующим фактическое имя C++ на основе параметров, которые применяются к функциям, не входящим в extern "C" {} (и, конечно, ко всем функциям-членам). В зависимости от используемых типов добавление ложной константы может фактически означать, что они считаются разными функциями и имеют разные декорированные имена, что может привести к ошибкам компоновщика.
@ jw013 правильный, void foo(int) и void foo(const int) - это одна и та же функция, а не перегрузки. ideone.com/npN4W4 ideone.com/tZav9R Здесь константа - это только деталь реализации тела функции и не влияет на разрешение перегрузки. Оставьте const в объявлении для более безопасного и аккуратного API, но поместите const в определение, если вы не собираетесь изменять скопированное значение.
Интересный @Oktalist. Однако я не уверен, что мне очень нравится идея о том, что определение не соответствует объявлению, даже если это разрешено компилятором.
@Oktalist @ jw013 Хорошо, значит, вы можете не совпадать с const в объявлении и определении. Это делает излишнюю const только ужасной вещью и ужасной ложью, потому что вы можете игнорировать ее в своем определении - см. Этот пример: class foo { void test(int * const pi); }; void foo::test(int *pi) { pi++; }
@Adisak Я знаю, что это старый, но я считаю, что правильное использование общедоступного API было бы наоборот. Таким образом, разработчики, работающие над внутренними компонентами, не допускают ошибок, таких как pi++, когда они не должны этого делать.
@CoffeeandCode Я вижу случайное использование const в реализации функции, если вы решите ограничить себя от изменения переменной по значению в реализации, но никогда нет причин иметь ложную и ненужную константу в заголовке / общедоступном API определение. Это ложь, потому что реализация может это игнорировать.
Не поймите меня неправильно, правильность констант - замечательная вещь, которую я ценю и рекомендую всем хорошим программистам, но важным является слово «правильность». Существует разница между «правильной» константой (которая фактически относится к принудительному контракту API) и «плохой» константой (той, которая не добавляет значения контракту API и не имеет значения для вызывающего кода, присутствует ли он вообще).
«Слишком много 'const' в API, когда оно не нужно, похоже на« плачущий волк », в конечном итоге люди начнут игнорировать 'const', потому что он повсюду и большую часть времени ничего не значит« Что? Суть константы в том, что ее нельзя игнорировать, потому что компилятор не позволит вам ее игнорировать.
@Taywee - компилятор позволяет вам игнорировать лишнюю константу в объявлении, потому что ваша реализация / определение функции может не включать константу (и я специально показываю случаи, когда компилятор проигнорирует это). Есть разница между необходимыми константами и лишними, и я указываю на них в своем ответе.
Это, безусловно, помогает выявлять тонкие ошибки, к которым склонны допускать люди, особенно когда использование констант согласовано в кодовой базе. один например. будет функцией std::stringc_str(), которая возвращает const char * для базовой строки. Кто-то ошибочно изменял передаваемую строку, когда это не предназначалось. Нас спасли компиляторы.
Добавление const к параметру передачи по значению загрязняет API реализацией. Он ничего не сообщает вызывающему и (как говорит @Adisak) загромождает API, затрудняя выбор важных частей. Мы никогда не разрешаем изменять параметры функции передачи по значению в теле, и это одна из ключевых проверок при проверке кода (вы делаете проверки кода?). Если вы хотите изменить значение, сделайте копию (и вы можете дать ей имя, которое документирует ваше использование).
@AbhinavGauniyal, используя "const char *" для строки, символы которой не должны изменяться, - это разумно. Это необходимо const. Однако примером ненужной константы в API может быть использование «const char * const» для той же строки или, что еще хуже, использование «char * const» для строки, символы которой функция изменила.
@IanBrockbank: да, обзоры кода важны для меня, настолько важны, что есть две итерации, одна компилятором, а другая - людьми. Пример, который я привел, был первым, и он отлично подходит для меня.
Голосование вниз, потому что большая часть этого ответа кажется разглагольствованием о чем-то, в чем вы ошиблись (параметры const в функции определение не влияют на API). На полпути вы признаете это, но было бы полезно, если бы вы отредактировали и либо удалили исходную неправильную часть, либо добавили в начале большой отказ от ответственности, говоря, что первая часть неверна.
Этот ответ бесполезен. По сути, это длинная и многословная тирада о том, что никто не делает и не предлагает делать. Это классический аргумент соломенного человека.
@melpomene Может быть, напыщенная речь, но не соломка - в вопросе была предложена концепция посторонних констант, и это напрямую касается этого вопроса. Поэтому ваше заявление о том, что никто не предлагает этого сделать, просто ложно. А анекдотическое предположение, что никто этого не делает (потому что я видел это в природе), логически является аргументом от незнания.
@Adisak Вопрос был про const по параметрам в определении функции. Первые три четверти вашего ответа посвящены const в API, то есть объявлениям функций. Есть небольшое отступление об исключении const из объявления, но затем оно уходит по касательной и говорит об исключении const только из определения (WTF?). Последний квартал посвящен тому, как создание переменной const означает, что вы не можете ее изменить, и если вам нужно, вы должны либо удалить const, либо сделать изменяемую копию. Классная история, но опять же не имеющая отношения к API или их разработчикам.
@melpomene Я согласен с тем, что это более широкое и глубокое наблюдение за ошибками ненужных констант, вызванных предлагаемым использованием таковых в API, а также с тем, что у меня есть целостное представление о том, что на реализацию функций влияет выбор в API . Мне жаль, что вы обеспокоены тем, что мой более широкий ответ не является более простым и ограниченным, чтобы соответствовать вашему вкусу.
@Adisak Никто не предлагал использовать const в API. Ваш «более широкий ответ» просто не отвечает на вопрос.
@melpomene Редко можно встретить первичную критику в ответ, потому что он слишком глубоко освещает тему, но не стесняйтесь писать свой собственный ответ с гораздо более узкими рамками, если вы считаете, что существует более правильный и сжатый ответ. Я оставлю это, потому что многие другие сочли его полезным.
@Adisak В четвертый раз ваш ответ не затрагивает «тему»; он полностью пропускает тему. Мне не нравится, как вы пытаетесь искажать мои слова. (Он «охватывает» совершенно другую тему, но не очень хорошо и глубоко; это много повторений и эмоционально заряженных слов (беспорядок, фильм плохой парень, глупый, ложь, ужасная вещь, ужасная ложь, бельмо на глазу, ...), но очень поверхностный техническое содержание.)
@melpomene Вы читали тот же текст, что и я? Кажется, вы ограничиваете следующий текст API только там, где, кажется, задается что-то более широкое: Как далеко вы зашли с const? Вы просто делаете функции константными, когда это необходимо, или вы делаете все возможное и используете его везде? Лично я предпочитаю широко использовать его, включая параметры, но в этом случае мне интересно, стоит ли оно того? - и мой ответ - используйте const столько, сколько вам нужно, но не тогда, когда это не нужно или вводит в заблуждение
Я видел слишком много вводящих в заблуждение модификаций аргументов внутри функций, чтобы поверить в необходимость использования аргумента const.
Я чувствую, что раздел макросов ослабляет вашу общую аргументацию. Если я использую XYZ с макросами и делаю ужасные вещи, это проблема макроса, а не проблема XYZ. Я думаю, тебе стоит убрать эту часть.
Решение "лишних констант" #define CDa const* #define CP *const #define CPD const * const * указатель на CONSTANT DATA * CONSTANT POINTER на данные * CONSTANT указатель на CONSTANT данные *** void mungerum(char CP buffer, char CPD mask, CN int count); vs void mungerum(char * const buffer, const char * const mask, const int count); Также устраняет путаницу с использованием const перед типом и после типа и обеспечивает меньшую многословность.
На мой взгляд, это лучший ответ, и в этом случае руководство по стилю Google также соглашается с ним: google.github.io/styleguide/cppguide.html#Use_of_const: «Для параметра функции, переданного по значению, const не влияет на вызывающую программу, поэтому не рекомендуется в объявлениях функций. См. TotW # 109.»
Я решил также добавить свой собственный ответ, который добавляет мои собственные слова, поддерживает @ Adisak, answer и цитирует Руководство по стилю Google C++ по этому вопросу: stackoverflow.com/questions/117293/….
Для первой части вашего ответа см. stackoverflow.com/questions/1143262/…, чтобы понять, почему реализация 1 не работает, но 2 можно скомпилировать. Но это все еще проблема реализации, а не API.
->* или .*, это обязательно.Это мешает вам написать что-то вроде
void foo(Bar *p) { if (++p->*member > 0) { ... } }
что я почти сделал прямо сейчас, и который, вероятно, не делает то, что вы намереваетесь.
То, что я хотел сказать, было
void foo(Bar *p) { if (++(p->*member) > 0) { ... } }
и если бы я поставил const между Bar * и p, компилятор сказал бы мне это.
Я бы немедленно проверил ссылку на приоритет операторов, когда я собираюсь смешать вместе такое количество операторов (если я еще не знаю 100%), так что IMO это не проблема.
Я бы разбил эту 1 сложную строку примерно на 5 или более кристально чистых и легко читаемых строк, каждая из которых имеет описательное имя переменной, которое делает всю эту операцию самодокументированной. Так что для меня это не проблема. Сжимать код в одну строку, когда ухудшается читаемость и закрадываются ошибки, на мой взгляд, не лучшая идея.
Может быть, это не веский аргумент. но если мы увеличим значение переменной const внутри компилятора функции, мы получим ошибку: «ошибка: увеличение параметра только для чтения». это означает, что мы можем использовать ключевое слово const как способ предотвратить случайное изменение наших переменных внутри функций (которые мы не должны использовать только для чтения). поэтому, если мы случайно сделали это во время компиляции, компилятор сообщит нам об этом. это особенно важно, если вы не единственный, кто работает над этим проектом.
Я знаю, что вопрос "немного" устарел, но когда я наткнулся на него, кто-то еще может сделать это в будущем ... ... все же я сомневаюсь, что бедняга перечислит здесь, чтобы прочитать мой комментарий :)
Мне кажется, что мы все еще слишком ограничены мышлением в стиле C. В парадигме ООП мы играем с объектами, а не с типами. Константный объект может концептуально отличаться от неконстантного объекта, в частности, в смысле логической константы (в отличие от побитовой константы). Таким образом, даже если постоянная правильность параметров функции является (возможно) чрезмерной осторожностью в случае POD, это не так в случае объектов. Если функция работает с константным объектом, это должно быть сказано об этом. Рассмотрим следующий фрагмент кода
#include <iostream>
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
class SharedBuffer {
private:
int fakeData;
int const & Get_(int i) const
{
std::cout << "Accessing buffer element" << std::endl;
return fakeData;
}
public:
int & operator[](int i)
{
Unique();
return const_cast<int &>(Get_(i));
}
int const & operator[](int i) const
{
return Get_(i);
}
void Unique()
{
std::cout << "Making buffer unique (expensive operation)" << std::endl;
}
};
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
void NonConstF(SharedBuffer x)
{
x[0] = 1;
}
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
void ConstF(const SharedBuffer x)
{
int q = x[0];
}
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
int main()
{
SharedBuffer x;
NonConstF(x);
std::cout << std::endl;
ConstF(x);
return 0;
}
ps: вы можете возразить, что ссылка (const) была бы более подходящей здесь и дает вам такое же поведение. Ну да ладно. Просто дать картину, отличную от того, что я мог видеть в другом месте ...
Будучи программистом VB.NET, которому необходимо использовать программу на C++ с более чем 50 открытыми функциями и файл .h, который время от времени использует квалификатор const, трудно понять, когда обращаться к переменной с помощью ByRef или ByVal.
Конечно, программа сообщает вам, генерируя ошибку исключения в строке, где вы допустили ошибку, но тогда вам нужно угадать, какой из 2-10 параметров неверен.
Итак, теперь у меня есть неприятная задача - попытаться убедить разработчика в том, что они действительно должны определять свои переменные (в файле .h) таким образом, чтобы можно было легко использовать автоматизированный метод создания всех определений функций VB.NET. Затем они самодовольно скажут: «Прочтите ... документацию».
Я написал сценарий awk, который анализирует файл .h и создает все команды Declare Function, но без индикатора того, какие переменные являются R / O или R / W, он выполняет только половину работы.
Обновлено:
По просьбе другого пользователя я добавляю следующее:
Вот пример (IMO) плохо сформированной записи .h;
typedef int (EE_STDCALL *Do_SomethingPtr)( int smfID, const char* cursor_name, const char* sql );
Результирующий VB из моего скрипта;
Declare Function Do_Something Lib "SomeOther.DLL" (ByRef smfID As Integer, ByVal cursor_name As String, ByVal sql As String) As Integer
Обратите внимание на отсутствие «const» в первом параметре. Без него программа (или другой разработчик) не имеет идеи, в качестве 1-го параметра следует передать «ByVal». Добавление «const» делает файл .h самодокументированным, так что разработчики, использующие другие языки, могут легко писать рабочий код.
@anatolyg, вы правы, это напыщенная речь, но она выявляет проблему с неправильным использованием концепций на языке, которые могут не потребоваться, но обеспечивают удобство использования и самостоятельную документацию. Если вы пишете код, который требует использования других разработчиков, важно сделать его пригодным для использования. Если кто-то сочтет это полезным, я с удовольствием опубликую сценарий AWK, но он работает только в том случае, если файл .h правильно сформирован.
Чтобы проиллюстрировать свою точку зрения, вам лучше опубликовать плохой пример *.h и хороший. А также переведенный файл, и покажите, где у скрипта были проблемы, и как проблемы исчезают, если вы используете const в нужном месте.
@anatolyg, так лучше?
По крайней мере, теперь я понимаю, что вы пытаетесь сказать! Но я не согласен: и int smfID, и const int smfID означают «передачу по значению» в C++, а int& smfID означает «передачу по ссылке».
@anatolyg В файле .h нет символа «&», и эти параметры, похоже, взаимозаменяемы для ByRef / ByVal. Единственный способ узнать это - либо поискать в документации .CHM-файла, либо подождать, пока моя программа не выйдет из строя с ошибкой «System.AccessViolationException», и попытаться выяснить, какой это параметр. Конкретная dll, которую я сейчас использую, имеет ~ 100 общедоступных методов, и я хотел бы автоматизировать создание «функций объявления».
Подвести итоги:
std::vector::at(size_type pos). То, что достаточно для стандартной библиотеки, хорошо для меня.«То, что достаточно хорошо для стандартной библиотеки, хорошо для меня» - не всегда верно. Например, стандартная библиотека постоянно использует уродливые имена переменных вроде _Tmp - вам это не нужно (на самом деле вам не разрешено их использовать).
@anatolyg это деталь реализации
Хорошо, и имена переменных, и типы с указанием const в списках аргументов являются деталями реализации. Я хочу сказать, что реализация стандартной библиотеки иногда бывает плохой. Иногда вы можете (и должны) добиться большего. Когда был написан код стандартной библиотеки - 10 лет назад? 5 лет назад (какие-то новейшие его части)? Сегодня мы можем писать лучший код.
Я не согласен. Файл .h также должен иметь определения const. В противном случае, если в функцию будут переданы константные параметры, компилятор выдаст ошибку, поскольку прототип в файле .h не имеет определений констант.