Использование 'const' для параметров функции
Как далеко вы идете с 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)
Есть ли причина для этого? Мне кажется немного необычным.
Ответы
Ответ 1
Причина в том, что const для параметра применяется только локально внутри функции, поскольку он работает с копией данных. Это означает, что сигнатура функции действительно одинакова. Это, вероятно, плохой стиль, чтобы сделать это много.
Я лично склонен не использовать const, кроме ссылочных и указательных параметров. Для скопированных объектов это не имеет особого значения, хотя это может быть более безопасным, поскольку оно сигнализирует о намерении внутри функции. Это действительно вызов. Я, как правило, использую const_iterator, хотя, когда вы зацикливаетесь на чем-то, и я не намерен его модифицировать, поэтому я думаю, что каждый его собственный, пока строго соблюдается корректность для ссылочных типов.
Ответ 2
"const бессмысленно, когда аргумент передается по значению, так как вы не будете изменять объект вызывающего объекта."
Неправильно.
Это о самодокументировании вашего кода и ваших предположений.
Если в вашем коде есть много людей, которые работают над ним, а ваши функции нетривиальны, вы должны пометить "const" все и все, что вы можете. При написании кода промышленной прочности вы всегда должны предполагать, что ваши коллеги - психопаты, пытающиеся получить вас любым способом (особенно потому, что они часто сами в будущем).
Кроме того, как упоминалось ранее, это может помочь компилятору немного оптимизировать вещи (хотя это длинный снимок).
Ответ 3
Иногда (слишком часто!) мне приходится распутывать код другого С++. И все мы знаем, что чей-то код на С++ является полным беспорядком почти по определению:) Итак, первое, что я делаю, чтобы расшифровать локальный поток данных, ставится const в каждом определении переменной до тех пор, пока компилятор не начнет лаять. Это также означает аргументы const-qualifying value, потому что это просто причудливые локальные переменные, инициализированные вызывающим.
А, я бы хотел, чтобы переменные были const по умолчанию и изменяемый был необходим для неконстантных переменных:)
Ответ 4
Следующие две строки функционально эквивалентны:
int foo (int a);
int foo (const int a);
Очевидно, вы не сможете изменить a
в теле foo
, если он определил второй способ, но нет никакой разницы со стороны.
Где const
действительно пригодится с параметрами ссылки или указателя:
int foo (const BigStruct &a);
int foo (const BigStruct *a);
Это говорит о том, что foo может принимать большой параметр, возможно, структуру данных, размер которой равен гигабайтам, без копирования. Кроме того, он говорит вызывающему: "Foo не изменит содержимое этого параметра". Передача ссылки на константу также позволяет компилятору принимать определенные решения о производительности.
*: Если он не отбрасывает константу, а другой пост.
Ответ 5
Дополнительные избыточные константы являются плохими с точки зрения API:
Внесение лишнего избыточного const в ваш код для параметров встроенного типа, передаваемых значением , загромождает ваш API, не делая никаких значимых обещаний пользователю или пользователю API (это только мешает реализации).
Слишком много "const" в API, когда это не нужно, похоже на " crying wolf", в конце концов люди начнут игнорировать "const", потому что они повсюду и ничего не значили в большинстве случаев.
Аргумент "reductio ad absurdum" для дополнительных констант в API хорош для этих первых двух точек: если больше константных параметров хороши, тогда каждый аргумент, который может иметь на нем const, ДОЛЖЕН иметь const. Фактически, если бы это было действительно так хорошо, вы бы хотели, чтобы параметр 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);
}
Послушайте, я так и реализовал это так, хотя я и обещал не делать этого - просто используя функцию обертки. Это похоже на то, когда плохой парень promises не убивает кого-то в фильме и приказывает своему приспешнику убить их вместо этого.
Эти лишние констелы стоят не больше, чем обещание от плохого парня.
Но умение лгать становится еще хуже:
Я был просветлен, что вы можете рассогласовать const в заголовке (декларации) и коде (определении), используя ложный 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 бросил лишнюю константу?
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, раздражающая наг, мелкое и бессмысленное обещание, ненужное препятствие и иногда приводит к очень опасным ошибкам.
Ответ 6
const должен был быть значением по умолчанию в С++.
Вот так:
int i = 5 ; // i is a constant
var int i = 5 ; // i is a real variable
Ответ 7
Когда я закодировал С++ для жизни, я констатировал все, что мог. Использование const - отличный способ помочь компилятору помочь вам. Например, константа возвращаемых значений метода может спасти вас от опечаток, например:
foo() = 42
когда вы имели в виду:
foo() == 42
Если foo() определено для возврата неконстантной ссылки:
int& foo() { /* ... */ }
Компилятор с радостью позволит вам назначить значение анонимному времени, возвращаемому вызовом функции. Создание его const:
const int& foo() { /* ... */ }
Устраняет эту возможность.
Ответ 8
Существует хорошая дискуссия по этой теме в старых статьях "Гуру недели" на comp.lang.С++. moderated здесь.
Соответствующая статья GOTW доступна на веб-сайте Herb Sutter здесь.
Ответ 9
Я использую const для параметров функции, которые являются ссылками (или указателями), которые являются только [in] данными и не будут изменены функцией. Значение, когда цель использования ссылки заключается в том, чтобы избежать копирования данных и не допускать изменения переданного параметра.
Помещение const в параметр boolean b в вашем примере только ограничивает реализацию и не вносит вклад в интерфейс класса (хотя обычно не рекомендуется изменять параметры).
Сигнатура функции для
void foo(int a);
и
void foo(const int a);
то же самое, что объясняет ваши .c и .h
Асаф
Ответ 10
Я говорю const ваши значения параметров.
Рассмотрим эту функцию:
bool isZero(int number)
{
if (number = 0) // whoops, should be number == 0
return true;
else
return false;
}
Если параметр number был const, компилятор остановится и предупредит нас об ошибке.
Ответ 11
Если вы используете операторы ->*
или .*
, это необходимо.
Это мешает вам писать что-то вроде
void foo(Bar *p) { if (++p->*member > 0) { ... } }
который я почти сделал прямо сейчас, и который, вероятно, не делает того, что вы намерены.
Я хотел сказать, что
void foo(Bar *p) { if (++(p->*member) > 0) { ... } }
и если бы я поставил const
между Bar *
и p
, компилятор сказал бы мне это.
Ответ 12
Ах, жесткий. С одной стороны, декларация является контрактом, и на самом деле не имеет смысла передавать аргумент const по значению. С другой стороны, если вы посмотрите на реализацию функции, вы даете компилятору больше шансов на оптимизацию, если вы объявите константу аргумента.
Ответ 13
const бесполезно, когда аргумент передается по значению, поскольку вы не будете изменять объект вызывающего объекта.
const должен быть предпочтительным при передаче по ссылке, если целью функции не является изменение переданного значения.
Наконец, функция, которая не изменяет текущий объект (это), может и, вероятно, должна быть объявлена const. Ниже приведен пример:
int SomeClass::GetValue() const {return m_internalValue;}
Это обещание не изменять объект, к которому применяется этот вызов. Другими словами, вы можете позвонить:
const SomeClass* pSomeClass;
pSomeClass->GetValue();
Если функция не была const, это приведет к предупреждению компилятора.
Ответ 14
Параметры значения маркировки "const" определенно являются субъективными.
Однако я действительно предпочитаю отмечать значения параметров const, как и в вашем примере.
void func(const int n, const long l) { /* ... */ }
Значение для меня ясно указывает на то, что функциональные значения параметров никогда не изменяются функцией. Они будут иметь такое же значение в начале, как и в конце. Для меня это часть стиля очень функционального программирования.
Для короткой функции это, вероятно, пустая трата времени/пространства, чтобы иметь здесь "const", поскольку обычно довольно очевидно, что аргументы не изменяются функцией.
Однако для большей функции это форма документации по реализации, и она выполняется компилятором.
Я могу быть уверен, что если я сделаю некоторые вычисления с 'n' и 'l', я смогу реорганизовать/переместить это вычисление, не опасаясь получить другой результат, потому что я пропустил место, где один или оба были изменены.
Поскольку это деталь реализации, вам не нужно объявлять значения параметров const в заголовке, так же, как вам не нужно объявлять параметры функции с теми же именами, что и в реализации.
Ответ 15
В случае, о котором вы упоминаете, это не влияет на вызывающих пользователей вашего API, поэтому он обычно не делается (и не нужен в заголовке). Это влияет только на реализацию вашей функции.
Это не особенно плохо, но преимущества не так уж велики, учитывая, что они не влияют на ваш API, и он добавляет типизацию, поэтому обычно это не делается.
Ответ 16
Я использую const, если смогу. Константа для параметров означает, что они не должны изменять свое значение. Это особенно важно при передаче по ссылке. const для функции объявляет, что функция не должна изменять члены классов.
Ответ 17
Я не использую const для параметра, переданного параметром. Вызывающему не важно, изменяете ли вы параметр или нет, это деталь реализации.
В действительности важно отметить методы как const, если они не изменяют свой экземпляр. Сделайте это, когда вы идете, потому что иначе вы могли бы получить либо много const_cast < > или вы могли бы обнаружить, что маркировка метода const требует изменения большого количества кода, потому что он вызывает другие методы, которые должны были быть отмечены как const.
Я также склонен отмечать локальные vars const, если мне не нужно их модифицировать. Я считаю, что это облегчает понимание кода, упрощая идентификацию "движущихся частей".
Ответ 18
Я стараюсь использовать const везде, где это возможно. (Или другое подходящее ключевое слово для целевого языка.) Я делаю это исключительно потому, что он позволяет компилятору делать дополнительные оптимизации, которые он не сможет сделать иначе. Поскольку я понятия не имею, что такое оптимизация, я всегда это делаю, даже если это кажется глупым.
Насколько мне известно, компилятор может очень хорошо увидеть параметр const value и сказать: "Эй, эта функция не меняет его, так что я могу пройти по ссылке и сохранить некоторые такты". Я не думаю, что когда-нибудь это будет сделано, так как это изменяет подпись функции, но в этом есть смысл. Может быть, он делает несколько манипуляций с стеком или что-то в этом роде... Дело в том, что я не знаю, но я знаю, что пытаюсь быть умнее, чем компилятор, только приводит к стыду.
В С++ есть дополнительный багаж с идеей const-correctness, поэтому он становится еще более важным.
Ответ 19
Возможно, это не будет правильным аргументом. но если мы увеличим значение переменной const внутри компилятора функции, мы получим ошибку:
" ошибка: добавление параметра только для чтения". так что это означает, что мы можем использовать ключевое слово const как способ предотвратить случайную модификацию наших переменных внутри функций (которые мы не должны использовать/только для чтения). поэтому, если мы случайно это сделали во время компиляции, компилятор сообщит об этом. это особенно важно, если вы не единственный, кто работает над этим проектом.
Ответ 20
Константный параметр полезен только тогда, когда параметр передается по ссылке, то есть как ссылку, так и указатель. Когда компилятор видит параметр const, он должен убедиться, что переменная, используемая в параметре, не изменяется внутри тела функции. Почему кто-то хочет сделать параметр by-value постоянным?: -)
Ответ 21
Если параметр передается по значению (и не является ссылкой), обычно нет большой разницы, объявлен ли параметр как const или нет (если он не содержит ссылочный элемент - не проблема для встроенных типов). Если параметр является ссылкой или указателем, обычно лучше защищать ссылочную/направленную память, а не сам указатель (я думаю, вы не можете сделать ссылку самой const, а не то, что она имеет большое значение, поскольку вы не можете изменить рефери),
Кажется хорошей идеей защищать все, что вы можете, как const. Вы можете опустить его, не опасаясь совершить ошибку, если параметры являются просто POD (включая встроенные типы), и нет никаких шансов, что они будут меняться дальше по дороге (например, в вашем примере параметр bool).
Я не знал о различите объявления файла .h/.cpp, но это имеет смысл. На уровне машинного кода ничто не является "const", поэтому, если вы объявляете функцию (в .h) как неконстантную, код такой же, как если бы вы объявили ее как const (оптимизация в сторону). Тем не менее, это поможет вам заручиться компилятором, что вы не измените значение переменной внутри реализации функции (.ccp). Это может пригодиться в случае, когда вы наследуете интерфейс, который позволяет изменять, но вам не нужно изменять параметр для достижения требуемой функциональности.
Ответ 22
О оптимизации компилятора: http://www.gotw.ca/gotw/081.htm
Ответ 23
Подводя итог:
- "Обычно const-by-value const недостоверна и в лучшем случае вводит в заблуждение". Из GOTW006
- Но вы можете добавить их в .cpp так же, как и с переменными.
- Обратите внимание, что стандартная библиотека не использует const. Например.
std::vector::at(size_type pos)
. Что хорошего для стандартной библиотеки хорошо для меня.
Ответ 24
Я бы не поставил константы на такие параметры - все уже знают, что логическое (в отличие от булева и amp;) постоянное, поэтому добавление его заставит людей подумать "подождите, что?" или даже что вы передаете параметр по ссылке.
Ответ 25
вещь, которую нужно запомнить с помощью const, заключается в том, что с самого начала проще сделать вещи const, чем пытаться их поместить позже.
Используйте const, когда вы хотите, чтобы что-то не изменилось - это дополнительный намек, который описывает, что делает ваша функция и что ожидать. Я видел много API C, которые могли бы сделать с некоторыми из них, особенно те, которые принимают c-строки!
Я был бы более склонным опускать ключевое слово const в файле cpp, чем заголовок, но, как я обычно вырезаю + вставить их, они будут храниться в обоих местах. Я понятия не имею, почему компилятор допускает это, я думаю, что это компилятор. Лучшая практика состоит в том, чтобы поставить ключевое слово const в оба файла.
Ответ 26
Все константы в ваших примерах не имеют никакой цели. По умолчанию С++ по умолчанию передается по значению, поэтому функция получает копии этих int и boolean. Даже если функция действительно их модифицирует, копия вызывающего абонента не изменяется.
Поэтому я бы избегал дополнительных констант, потому что
- Они redudant
- Они загромождают
текст
- Они мешают мне
изменение переданного значения в
случаи, когда это может быть полезно или эффективно.
Ответ 27
На самом деле нет причин для создания параметра значения "const", поскольку функция может в любом случае модифицировать копию переменной.
Причиной использования "const" является то, что вы передаете что-то большее (например, структуру с множеством членов) по ссылке, и в этом случае оно гарантирует, что функция не может ее изменить; или, скорее, компилятор будет жаловаться, если вы попытаетесь изменить его обычным способом. Это предотвращает случайное изменение.
Ответ 28
Поскольку параметры передаются по значению, это не имеет никакого значения, если вы указываете const или нет с точки зрения функции вызова. В принципе не имеет смысла объявлять pass по параметрам значения как const.
Ответ 29
Я знаю, что вопрос "немного" устарел, но по мере того, как я натолкнулся на него, кто-то еще может это сделать и в будущем...... все же я сомневаюсь, что бедный парень перечислит здесь, чтобы прочитать мой комментарий:)
Мне кажется, что мы все еще слишком ограничены способом мышления в стиле С. В парадигме ООП мы играем с объектами, а не с типами. Объект Const может быть концептуально отличным от неконстантного объекта, в частности, в смысле логического-const (в отличие от bitwise-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) здесь будет более уместной и даст вам такое же поведение. Ну, ладно. Просто давая другую картину из того, что я мог видеть в другом месте...
Ответ 30
Будучи программистом VB.NET, которому необходимо использовать программу на С++ с 50 + открытыми функциями и файл .h, который спорадически использует спецификатор const, трудно узнать, когда обращаться к переменной с помощью ByRef или ByVal.
Конечно, программа сообщает вам, генерируя ошибку исключения в строке, где вы допустили ошибку, но тогда вам нужно угадать, какой из 2-10 параметров неверен.
Итак, теперь у меня неприятная задача - убедить разработчика, что они должны действительно определять свои переменные (в файле .h) таким образом, чтобы легко использовать автоматизированный метод создания всех определений функций VB.NET. Затем они самодовольно скажут: "прочитайте... документацию".
Я написал awk script, который анализирует файл .h и создает все команды Declare Function, но без индикатора относительно того, какие переменные являются R/O vs R/W, он выполняет только половину задания.
EDIT:
При поддержке другого пользователя я добавляю следующее:
Вот пример плохо сформированной записи (.HO) (IMO);
typedef int (EE_STDCALL *Do_SomethingPtr)( int smfID, const char* cursor_name, const char* sql );
Результат VB из моего script;
Declare Function Do_Something Lib "SomeOther.DLL" (ByRef smfID As Integer, ByVal cursor_name As String, ByVal sql As String) As Integer
Обратите внимание на отсутствие "const" в первом параметре. Без него программа (или другой разработчик) не имеет идеи, первый параметр должен быть передан "ByVal". Добавляя "const", он сам документирует файл .h, чтобы разработчики, использующие другие языки, могли легко писать рабочий код.