Использование const для параметров функции


397

Как далеко вы идете с 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)

Есть причина для этого? Это кажется немного необычным для меня.


Я не согласен. Файл .h также должен иметь определения const. Если нет, то если в функцию передаются параметры const, компилятор выдаст ошибку, так как у прототипа в файле .h нет определений const.
Селвин

10
Согласен. :-) (С вопросом, а не последним комментарием!) Если значение не должно быть изменено в теле функции, это может помочь остановить глупые == или = ошибки, вы никогда не должны ставить const в обоих, (если это передается по значению, вы должны иначе) Это не достаточно серьезно, чтобы спорить об этом, хотя!
Крис Хуан-Ливер,

19
@selwyn: Даже если вы передадите const int функции, она будет скопирована (так как это не ссылка), и поэтому const-ness не имеет значения.
jalf

1
То же самое происходит и в этом вопросе: stackoverflow.com/questions/1554750/…
частично

5
Я понимаю, что этому посту пару лет, но как новый программист, я задавался этим вопросом и наткнулся на этот разговор. По моему мнению, если функция не должна изменять значение, будь то ссылка или копия значения / объекта, она должна быть константой. Это безопаснее, самодокументируется и более дружественно к отладке. Даже для самой простой функции, которая имеет одно утверждение, я все еще использую const.

Ответы:


187

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

Лично я склонен не использовать const за исключением параметров ссылки и указателя. Для скопированных объектов это на самом деле не имеет значения, хотя может быть более безопасным, поскольку сигнализирует о намерениях внутри функции. Это действительно суждение. Однако я склонен использовать const_iterator, когда зацикливаюсь на чем-то, и я не собираюсь его модифицировать, поэтому я предполагаю, что каждый из них сам по себе, до тех пор, пока строго соблюдается правильность констант для ссылочных типов.


57
Я не могу согласиться с частью «плохого стиля». Удаление constиз прототипов функций имеет то преимущество , что вам не нужно , чтобы изменить файл заголовок , если вы решили отказаться constот части реализации позже.
Михал Гурни

4
«Лично я не использую const, за исключением параметров ссылки и указателя». Возможно, вам следует уточнить, что «я склонен не использовать лишние квалификаторы в объявлениях функций, а использовать constтам, где это имеет полезное значение».
Дедупликатор

3
Я не согласен с этим ответом. Я наклоняюсь в другую сторону и отмечаю параметры, constкогда это возможно; это более выразительно. Когда я читаю чужой код, я использую маленькие индикаторы, подобные этому, чтобы судить, насколько тщательно они уделяют написанию своего кода наряду с такими вещами, как магические числа, комментарии, правильное использование указателей и т. Д.
Ultimater

4
int getDouble(int a){ ++a; return 2*a; }Попробуй это. Конечно, там ++aнечего делать, но это можно найти в длинной функции, написанной более чем одним программистом в течение длительного периода времени. Я настоятельно рекомендую написать, int getDouble( const int a ){ //... }что при поиске будет сгенерирована ошибка компиляции ++a;.
dom_beau

3
Все зависит от того, кому нужна какая информация. Вы предоставляете параметр по значению, поэтому вызывающей стороне не нужно ничего знать о том, что вы делаете (внутренне) с ним. Так что пишите class Foo { int multiply(int a, int b) const; }в шапке. В вашей реализации вы делаете уход , что вы можете обещать не изменять aи bпоэтому int Foo::multiply(const int a, const int b) const { }имеет смысл здесь. (Замечание: и вызывающая сторона, и реализация заботятся о том, чтобы функция не изменяла свой Fooобъект, то есть const в конце своего объявления)
CharonX

415

«const не имеет смысла, когда аргумент передается по значению, так как вы не будете изменять объект вызывающего».

Неправильно.

Речь идет о самодокументировании вашего кода и ваших предположений.

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

Кроме того, как кто-то упоминал ранее, это может помочь компилятору немного оптимизировать вещи (хотя это далеко).


41
Полностью согласен. Все дело в общении с людьми и ограничении того, что можно сделать с помощью переменной, тем, что следует сделать.
Лен Холгейт

19
Я проголосовал за это. Я думаю, что вы разбавляете то, что пытаетесь указать с помощью const, когда применяете это к простым аргументам передачи по значению.
Тони

26
Я проголосовал за это. Объявление параметра «const» добавляет семантическую информацию к параметру. Они подчеркивают, что изначально задумывал автор кода, и с течением времени это поможет в обслуживании кода.
Ричард Корден

13
@tonylo: вы неправильно поняли. Речь идет о маркировке локальной переменной как const внутри блока кода (который является функцией). Я бы сделал то же самое для любой локальной переменной. Это ортогонально наличию API, который является константно-корректным, что действительно важно.
rlerallut

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

157

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

Ах, я хочу переменные были Const по умолчанию и изменяемые требуется для неконстантных переменных :)


4
«Я бы хотел, чтобы переменные были постоянными по умолчанию» - оксюморон ?? 8-) Серьезно, как «все» помогает вам распутать код? Если оригинальный автор изменил предположительно постоянный аргумент, как вы узнали, что переменная должна быть постоянной? Более того, подавляющее большинство (не аргументных) переменных должны быть ... переменными. Так что компилятор должен сломаться очень скоро после того, как вы начали процесс, не так ли?
ysap

9
@ysap, 1. Маркировка const как можно больше позволяет мне видеть, какие части движутся, а какие нет. По моему опыту, многие местные жители де-факто постоянны, а не наоборот. 2. «Константа-переменная» / «Неизменяемая переменная» может звучать как оксюморон, но является стандартной практикой в ​​функциональных языках, а также в некоторых нефункциональных; см. Rust, например: doc.rust-lang.org/book/variable-bindings.html
Константин

1
Также стандарт теперь в некоторых случаях в C ++; например лямбда [x](){return ++x;}- это ошибка; смотрите здесь
анатолыг

10
Переменные constпо умолчанию "" в Rust :)
феникс

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

80

Следующие две строки функционально эквивалентны:

int foo (int a);
int foo (const int a);

Очевидно, что вы не сможете изменить aтело, fooесли оно определено вторым способом, но снаружи нет никакой разницы.

Что constдействительно пригодится, так это параметры ссылки или указателя:

int foo (const BigStruct &a);
int foo (const BigStruct *a);

Это говорит о том, что foo может принимать большой параметр, возможно, структуру данных размером в гигабайты, не копируя его. Кроме того, он говорит вызывающей стороне: «Foo не * изменит содержимое этого параметра». Передача константной ссылки также позволяет компилятору принимать определенные решения о производительности.

*: Если это не отбрасывает постоянство, но это другой пост.


3
Это не то, о чем этот вопрос; Конечно, для аргументов, на которые указывают ссылки, или аргументов, на которые указывают ссылки, рекомендуется использовать const (если значение, на которое указывает ссылка или на которое указано, не изменяется). Обратите внимание, что это не параметр, который является константой в вашем примере указателя; это то, на что указывает параметр.
Tml

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

73

Слишком много лишних const плохо с точки зрения API:

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

Слишком много «const» в API, когда оно не нужно, похоже на « плачущего волка », в конце концов, люди начнут игнорировать «const», потому что он повсюду и ничего не значит в большинстве случаев.

Аргумент "reductio ad absurdum" к дополнительным концам в API хорош для этих первых двух пунктов: если больше параметров const хороши, то каждый аргумент, который может иметь const, ДОЛЖЕН иметь const на нем. На самом деле, если бы это было действительно так хорошо, вы бы хотели, чтобы const был параметром по умолчанию для параметров и имел бы ключевое слово типа «изменяемый» только тогда, когда вы хотите изменить параметр.

Итак, давайте попробуем положить в const, где мы можем:

void mungerum(char * buffer, const char * mask, int count);

void mungerum(char * const buffer, const char * const mask, const int count);

Рассмотрим строку кода выше. Не только объявление более загромождено, длиннее и труднее для чтения, но и пользователь API может безопасно игнорировать три из четырех ключевых слов «const». Однако дополнительное использование const сделало вторую строку потенциально ОПАСНОЙ!

Почему?

Быстрое неверное прочтение первого параметра char * const bufferможет заставить вас подумать, что он не изменит память в буфере данных, который передается, однако это не так! Избыточное «const» может привести к опасным и неверным предположениям о вашем API при сканировании или неправильном прочтении.


Лишние const также плохи с точки зрения реализации кода:

#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 в заголовке (объявление) и коде (определение) с помощью ложного const. Адвокаты, довольные константой, утверждают, что это хорошо, так как позволяет использовать констант только в определении.

// Example of const only in definition, not declaration
class foo { void test(int *pi); };
void foo::test(int * const pi) { }

Тем не менее, обратное утверждение верно ... вы можете поместить ложный констант только в объявлении и игнорировать его в определении. Это только делает излишнее const в API более ужасной вещью и ужасной ложью - см. Этот пример:

class 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 добавил лишнее const?

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

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


9
Почему отрицательные? Гораздо полезнее, если вы оставите краткий комментарий к понижению.
Adisak

7
Весь смысл использования аргумента const состоит в том, чтобы сделать отмеченную строку неудачной (plist = pnext). Разумная мера безопасности - сохранять неизменность аргумента функции. Я согласен с вашей точкой зрения, что они плохи в объявлениях функций (поскольку они излишни), но они могут служить своим целям в блоке реализации.
touko

23
@ Adisak Я не вижу ничего плохого в вашем ответе как таковом, но из ваших комментариев видно, что вы упускаете важный момент. Определение / реализация функции не является частью API, которая является только объявлением функции . Как вы сказали, объявление функций с константными параметрами не имеет смысла и добавляет беспорядок. Однако пользователям API, возможно, никогда не понадобится видеть его реализацию. В то же время, разработчик может решить сделать некоторые параметры в определении функции ТОЛЬКО для ясности, что совершенно нормально.
jw013

17
@ jw013 является правильным void foo(int)и void foo(const int)является точно такой же функцией, а не перегрузками. ideone.com/npN4W4 ideone.com/tZav9R Здесь const является лишь подробностью реализации тела функции и не влияет на разрешение перегрузки. Оставьте const вне объявления для более безопасного и аккуратного API, но добавьте const в определение , если вы не собираетесь изменять скопированное значение.
Окталист

3
@ Adisak Я знаю, что это старый, но я считаю, что правильное использование для публичного API будет наоборот. Таким образом, разработчики, работающие над внутренними pi++компонентами, не допускают ошибок, например, когда они не должны этого делать.
CoffeeandCode

39

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

int i = 5 ; // i is a constant

var int i = 5 ; // i is a real variable

8
Именно мои мысли.
Константин

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

4
Интересно, я никогда не думал об этом.
Дан

6
Точно так же, unsignedдолжно было быть по умолчанию в C ++. Как это: int i = 5; // i is unsignedи signed int i = 5; // i is signed.
hkBattousai

25

Когда я кодировал C ++ для жизни, я использовал все, что мог. Использование const - отличный способ помочь компилятору помочь вам. Например, использование возвращаемых значений вашего метода может спасти вас от опечаток, таких как:

foo() = 42

когда вы имели в виду:

foo() == 42

Если функция foo () определена для возврата неконстантной ссылки:

int& foo() { /* ... */ }

Компилятор с радостью позволит вам присвоить значение анонимному временному объекту, возвращаемому вызовом функции. Делая это const:

const int& foo() { /* ... */ }

Устраняет эту возможность.


6
С каким компилятором это работало? GCC выдает ошибку при попытке компиляции foo() = 42: error: lvalue требуется в качестве левого операнда присваивания
gavrie

Это просто неверно. foo () = 42 - это то же самое, что 2 = 3, т.е. ошибка компилятора. И возвращать const совершенно бессмысленно. Это ничего не делает для встроенного типа.
Джош

2
Я сталкивался с этим использованием const, и я могу вам сказать, в конце концов, это приносит больше хлопот, чем преимуществ. Подсказка: const int foo()это другой тип, чем int foo(), который доставляет вам большие неприятности, если вы используете такие вещи, как указатели функций, системы сигналов / слотов или boost :: bind.
Мефане

2
Я исправил код, чтобы включить эталонное возвращаемое значение.
Авди

Разве это не const int& foo()то же самое int foo(), что из-за оптимизации возвращаемого значения?
Зантье

15

На эту тему есть хорошее обсуждение в старых статьях "Гуру недели" на comp.lang.c ++., Модерируемых здесь .

Соответствующая статья GOTW доступна на веб-сайте Херба Саттера здесь .


1
Херб Саттер - действительно умный парень :-) Определенно стоит прочитать, и я согласен со ВСЕМИ его пунктами.
Адисак

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

9

Я говорю const ваши значения параметров.

Рассмотрим эту функцию с ошибками:

bool isZero(int number)
{
  if (number = 0)  // whoops, should be number == 0
    return true;
  else
    return false;
}

Если бы параметр number был const, компилятор остановился бы и предупредил нас об ошибке.


2
другим способом является использование if (0 == число) ... else ...;
Йоханнес Шауб -

5
@ ChrisHuang-Leaver Ужасно это не так, если вы говорите, как Йода, вы делаете: stackoverflow.com/a/2430307/210916
MPelletier

GCC / Clang -Wall дает вам -Wpherheses, который требует от вас сделать это «if ((number = 0))», если это действительно то, что вы намеревались сделать. Который работает как замена для того, чтобы быть Йодой.
Jetski S-type

8

Я использую const для параметров функции, которые являются ссылками (или указателями), которые являются только [in] данными и не будут изменены функцией. Это означает, что когда цель использования ссылки состоит в том, чтобы избежать копирования данных и не допустить изменения переданного параметра.

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

Подпись функции для

void foo(int a);

а также

void foo(const int a);

то же самое, что объясняет ваши .c и .h

Асаф


6

Если вы используете операторы ->*или .*, это обязательно.

Это мешает вам написать что-то вроде

void foo(Bar *p) { if (++p->*member > 0) { ... } }

что я почти сделал прямо сейчас, и который, вероятно, не делает то, что вы собираетесь.

То, что я хотел сказать, было

void foo(Bar *p) { if (++(p->*member) > 0) { ... } }

и если бы я поставил constмежду Bar *и p, компилятор сказал бы мне это.


4
Я бы сразу проверил ссылку на приоритет операторов, когда собираюсь объединить столько операторов (если я еще не знаю 100%), так что IMO это не проблема.
mk12

5

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


5

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

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

Наконец, функция, которая не изменяет текущий объект (this), может и, вероятно, должна быть объявлена ​​как const. Пример ниже:

int SomeClass::GetValue() const {return m_internalValue;}

Это обещание не изменять объект, к которому применяется этот вызов. Другими словами, вы можете позвонить:

const SomeClass* pSomeClass;
pSomeClass->GetValue();

Если функция не является константой, это приведет к предупреждению компилятора.


5

Маркировка значений параметров «const» - определенно субъективная вещь.

Однако я предпочитаю отмечать значения параметров const, как в вашем примере.

void func(const int n, const long l) { /* ... */ }

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

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

Однако для более крупной функции это форма документации по реализации, и она обеспечивается компилятором.

Я могу быть уверен, что если я сделаю какое-то вычисление с 'n' и 'l', я смогу реорганизовать / переместить это вычисление, не боясь получить другой результат, потому что я пропустил место, где один или оба изменены.

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


4

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


3

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

Насколько я знаю, компилятор мог бы очень хорошо видеть параметр константного значения и сказать: «Эй, эта функция все равно не изменяет его, поэтому я могу перейти по ссылке и сохранить некоторые тактовые циклы». Я не думаю, что это когда-либо сделало бы такую ​​вещь, так как это изменяет сигнатуру функции, но это имеет значение. Может быть, он выполняет какие-то другие манипуляции со стеком или что-то в этом роде ... Дело в том, что я не знаю, но я знаю, что попытка быть умнее компилятора только приводит к тому, что мне стыдно.

С ++ имеет дополнительный багаж с идеей правильности, поэтому он становится еще более важным.


Хотя в некоторых случаях это может помочь, я подозреваю, что возможность продвижения оптимизаций сильно преувеличена const. Скорее, это вопрос определения намерения в реализации и последующего перехвата мыслей (случайное увеличение неправильной локальной переменной, потому что это не так const). Параллельно, я бы также добавил, что компиляторы могут изменять сигнатуры функций в том смысле, что функции могут быть встроенными, а однажды встроенными может быть изменен весь способ их работы; добавление или удаление ссылок, создание литералов «переменных» и т. д. находятся в рамках правила «как если бы»
underscore_d

3

1. Лучший ответ на основе моей оценки:

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

2. Мои собственные слова (согласен с лучшим ответом):

  1. Для передачи по значению нет никакой выгоды для добавления const. Все, что он делает, это:
    1. ограничить реализацию, чтобы он должен был делать копию каждый раз, когда он хочет изменить входной параметр в исходном коде (это изменение не будет иметь побочных эффектов, так как то, что передается, уже является копией, так как оно передается по значению). И часто изменение входного параметра, который передается по значению, используется для реализации функции, поэтому добавление constвезде может помешать этому.
    2. и добавление constненужного загромождает код с consts везде, отвлекая внимание от consts, которые действительно необходимы для безопасного кода.
  2. Однако, когда имеешь дело с указателями или ссылками , constэто критически важно при необходимости и должно использоваться, так как оно предотвращает нежелательные побочные эффекты с постоянными изменениями вне функции, и поэтому каждый отдельный указатель или ссылка должны использоваться, constкогда параметр является только входом, не выход. Использование const только параметров, передаваемых по ссылке или указателю, дает дополнительное преимущество, так как делает очевидным, какие параметры являются указателями или ссылками. Это еще одна вещь, которую нужно выделить и сказать: «Берегись! Любой параметр constрядом с ним - это ссылка или указатель!».
  3. То, что я описал выше, часто было консенсусом, достигнутым в профессиональных организациях по разработке программного обеспечения, в которых я работал, и считалось наилучшей практикой. Иногда даже правило было строгим: «никогда не используйте const для параметров, которые передаются по значению, но всегда используйте его для параметров, передаваемых по ссылке или указателю, только если они являются входными данными».

3. Слова Google (согласен со мной и лучший ответ):

(Из « Руководства по стилю Google C ++ »)

Для параметра функции, передаваемого по значению, const не влияет на вызывающую функцию, поэтому не рекомендуется в объявлениях функций. Смотрите TotW # 109 .

Использование const для локальных переменных не поощряется и не поощряется.

Источник: раздел «Использование const» в Руководстве по стилю Google C ++: https://google.github.io/styleguide/cppguide.html#Use_of_const . Это действительно очень ценный раздел, поэтому прочитайте весь раздел.

Обратите внимание, что "TotW # 109" означает "Совет недели # 109: значимый constв объявлениях функций" , а также является полезным чтением. Он более информативен и менее директивен в отношении того, что делать, и на основе контекста появился до того, constкак вышеприведенное правило «Руководства по стилю Google C ++» было приведено выше, но из-за его ясности constприведенное выше правило было добавлено в Google C ++. Гид по стилю.

Также обратите внимание, что хотя я цитирую Руководство по стилю Google C ++ здесь в защиту своей позиции, это НЕ означает, что я всегда следую этому руководству или всегда рекомендую следовать этому руководству. Некоторые из вещей , которые они рекомендуют являются просто странны, такими , как их kDaysInAWeek-style именования для «Постоянных имен» . Тем не менее, тем не менее, полезно и уместно указать, что одна из самых успешных и влиятельных в мире технических и программных компаний использует то же обоснование, что и я, и другие, такие как @Adisak, чтобы поддержать наши взгляды на этот вопрос.

4. У Кланга Линтера clang-tidyесть несколько вариантов:

A. Кроме того , стоит отметить , что ЛИНТЕР звона, в clang-tidy, имеет вариант, readability-avoid-const-params-in-decls, описанные здесь , чтобы поддержать исполнение в кодовой базе не используя constдля передачи по значению параметров функции :

Проверяет, есть ли в объявлении функции параметры верхнего уровня.

Значения const в объявлениях не влияют на сигнатуру функции, поэтому их не следует помещать туда.

Примеры:

void f(const string);   // Bad: const is top level.
void f(const string&);  // Good: const is not top level.

И вот еще два примера, которые я добавляю себе для полноты и ясности:

void f(char * const c_string);   // Bad: const is top level. [This makes the _pointer itself_, NOT what it points to, const]
void f(const char * c_string);   // Good: const is not top level. [This makes what is being _pointed to_ const]

Б. У этого также есть эта опция: readability-const-return-type- https://clang.llvm.org/extra/clang-tidy/checks/readability-const-return-type.html

5. Мой прагматичный подход к тому, как я бы сформулировал руководство по стилю по этому вопросу:

Я просто скопировал бы и вставил это в мое руководство по стилю:

[КОПИЯ / НАЧАЛО СТРАНИЦЫ]

  1. Всегда используйте const параметры функции, передаваемые по ссылке или указателю, когда их содержимое (на что они указывают) НЕ предназначены для изменения. Таким образом, это становится очевидным, когда переменная, переданная по ссылке или указателю, ожидается изменить, потому что она будет отсутствовать const. В этом случае использования constпредотвращает случайные побочные эффекты за пределами функции.
  2. Он не рекомендуется для использования constна параметры функции передаются по значению, потому что constне имеет никакого влияния на вызывающей: даже если переменная изменяется в функции не будет никаких побочных эффектов за пределами функции. См. Следующие ресурсы для дополнительного обоснования и понимания:
    1. Раздел «Руководство по стилю Google C ++» «Использование const»
    2. «Совет недели № 109: значимый constв объявлениях функций»
    3. Ответ Adisak на переполнение стека: «Использование const для параметров функции»
  3. « Никогда не используйте верхний уровень const[то есть: constдля параметров, передаваемых по значению ], для параметров функции в объявлениях, которые не являются определениями (и будьте осторожны, чтобы не копировать / вставлять бессмысленные const). Это бессмысленно и игнорируется компилятором, это визуальный шум и это может ввести читателей в заблуждение »( https://abseil.io/tips/109 , выделение текста добавлено).
    1. Единственные constквалификаторы, которые влияют на компиляцию, это те, которые помещены в определение функции, а НЕ в предварительное объявление функции, например в объявление функции (метода) в заголовочном файле.
  4. Никогда не используйте верхний уровень const[то есть: constдля переменных, переданных по значению ] для значений, возвращаемых функцией.
  5. Использование constуказателей или ссылок, возвращаемых функцией, зависит от разработчика , поскольку это иногда полезно.
  6. TODO: применить некоторые из вышеперечисленных с помощью следующих clang-tidyпараметров:
    1. https://clang.llvm.org/extra/clang-tidy/checks/readability-avoid-const-params-in-decls.html
    2. https://clang.llvm.org/extra/clang-tidy/checks/readability-const-return-type.html

Вот несколько примеров кода, демонстрирующих constправила, описанные выше:

constПримеры параметров:
(некоторые заимствованы отсюда )

void f(const std::string);   // Bad: const is top level.
void f(const std::string&);  // Good: const is not top level.

void f(char * const c_string);   // Bad: const is top level. [This makes the _pointer itself_, NOT what it points to, const]
void f(const char * c_string);   // Good: const is not top level. [This makes what is being _pointed to_ const]

constПримеры типов возвращаемых данных:
(некоторые заимствованы отсюда )

// BAD--do not do this:
const int foo();
const Clazz foo();
Clazz *const foo();

// OK--up to the implementer:
const int* foo();
const int& foo();
const Clazz* foo();

[КОПИЯ / ВСТАВИТЬ КОНЕЦ]


2

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

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


2

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

Что действительно важно, так это пометить методы как const, если они не изменяют свой экземпляр. Делайте это по ходу дела, потому что в противном случае вы можете получить либо много const_cast <>, либо вы можете обнаружить, что маркировка метода const требует изменения большого количества кода, потому что он вызывает другие методы, которые должны были быть помечены как const.

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



2

Я использую const, где могу. Const для параметров означает, что они не должны изменять свое значение. Это особенно ценно при передаче по ссылке. const for function объявляет, что функция не должна изменять членов классов.


2

Обобщить:

  • «Обычно const передача по значению не используется и в лучшем случае вводит в заблуждение». От GOTW006
  • Но вы можете добавить их в .cpp так же, как и с переменными.
  • Обратите внимание, что стандартная библиотека не использует const. Например std::vector::at(size_type pos). То, что достаточно для стандартной библиотеки, хорошо для меня.

2
«То, что достаточно для стандартной библиотеки, хорошо для меня», это не всегда так. Например, стандартная библиотека использует уродливые имена переменных как и _Tmpвсегда - вы этого не хотите (на самом деле вы не можете их использовать).
Анатолий

1
@anatolyg это деталь реализации
Фернандо

2
ОК, имена переменных и типы с константными квалификациями в списках аргументов являются деталями реализации. Я хочу сказать, что реализация стандартной библиотеки иногда не очень хороша. Иногда вы можете (и должны) сделать лучше. Когда был написан код для стандартной библиотеки - 10 лет назад? 5 лет назад (некоторые его новые части)? Мы можем написать лучший код сегодня.
Анатолий

1

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

Я не знал о разнице в объявлении файла .h / .cpp, но в этом есть какой-то смысл. На уровне машинного кода ничто не является «const», поэтому, если вы объявляете функцию (в .h) как неконстантную, код такой же, как если бы вы объявляли ее как const (без оптимизации). Тем не менее, это поможет вам подключить компилятор, чтобы вы не меняли значение переменной внутри реализации функции (.ccp). Это может пригодиться в том случае, если вы наследуете интерфейс, который позволяет вносить изменения, но вам не нужно менять параметр для достижения требуемой функциональности.


0

Я бы не стал использовать const для таких параметров - все уже знают, что логическое значение (в отличие от логического &) является константой, поэтому добавление его заставит людей подумать «подождите, что?» или даже что вы передаете параметр по ссылке.


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

0

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

Используйте const, когда вы хотите, чтобы что-то не изменилось - это дополнительная подсказка, которая описывает, что делает ваша функция и чего ожидать. Я видел много C API, которые могли бы с некоторыми из них, особенно те, которые принимают c-строки!

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


Я не понимаю этого вообще. Почему вы склонны опускать его в файле cpp (определение функции)? Вот где это действительно что-то значит и может ловить ошибки. Как вы думаете, почему лучше использовать const в обоих местах? В заголовочном файле (объявление функции) это ничего не значит и загромождает API. Может быть, есть небольшая ценность того, чтобы decl и defn выглядели одинаково, но мне кажется, что это действительно незначительное преимущество по сравнению с проблемой загромождения API.
Дон Хэтч

@ DonHatch 8 лет спустя, вау. В любом случае, как сказал ОП, «я также был удивлен, узнав, что вы можете опустить const в параметрах в объявлении функции, но можете включить его в определение функции».
gbjbaanb

0

На самом деле нет смысла делать параметр-значение «const», так как функция все равно может только изменять копию переменной.

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


0

Параметр Const полезен только тогда, когда параметр передается по ссылке, т. Е. По ссылке или по указателю. Когда компилятор видит параметр const, он проверяет, что переменная, используемая в параметре, не изменяется в теле функции. Почему кто-то хочет сделать параметр по значению как константу? :-)


По многим причинам. Создание параметра по значению constясно говорит: «Мне не нужно изменять это, поэтому я заявляю об этом. Если я попытаюсь изменить его позже, выдайте мне ошибку во время компиляции, чтобы я мог исправить свою ошибку или снять пометку как const. ' Так что это вопрос гигиены кода и безопасности. Для всего, что нужно, чтобы добавить к файлам реализации, это должно быть то, что люди делают как чистый рефлекс, IMO.
underscore_d

0

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


0

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

Так что я бы избегал лишних конст

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

-1

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

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

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

Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.