Использование модификатора 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)

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

30 ответов


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

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


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

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

Это про самодокументируемый код и ваши предположения.

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

кроме того, как кто-то упомянул ранее, might помогите компилятору немного оптимизировать вещи (хотя это длинный выстрел).


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

Ах, я бы хотел, чтобы переменные были const по умолчанию mutable требуются для не-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 плохи с точки зрения API:

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

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

аргумент "reductio ad absurdum" для дополнительных const в API хорош для этих первых двух точек: если больше параметров const хороши, то каждый аргумент, который может иметь 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 при сканировании или быстро понял.


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


const должен был быть по умолчанию в C++. Вот так:

int i = 5 ; // i is a constant

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

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

foo() = 42

если вы имели в виду:

foo() == 42

если Foo () определено для возврата ссылки non-const:

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

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

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

исключает такую возможность.


есть хорошая дискуссия на эту тему в старых статьях "гуру недели" на comp.ленг.с.++модерировал здесь.

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


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

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

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

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


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

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

сигнатура функции для

void foo(int a);

и

void foo(const int a);

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

Асаф


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

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

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

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


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

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

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

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

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

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

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

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


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

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

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

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

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

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

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


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


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

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


Я не использовать const для значений-прошло parametere. Вызывающему объекту все равно, изменяете вы параметр или нет, это деталь реализации.

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

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


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

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

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


по оптимизации компилятора:http://www.gotw.ca/gotw/081.htm


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


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

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


все запоры в ваших примерах не имеют цели. C++ по умолчанию является pass-by-value, поэтому функция получает копии этих ints и booleans. Даже если функция изменяет их, копия вызывающего абонента не затрагивается.

поэтому я бы избегал дополнительных запоров, потому что

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

подведем итоги:

  • "обычно const pass-by-value является бесполезным и вводящим в заблуждение в лучшем случае.- От GOTW006
  • но вы можете добавить их .cpp, как вы сделали бы с переменными.
  • обратите внимание, что стандартная библиотека не использовать const. Е. Г. std::vector::at(size_type pos). То, что достаточно хорошо для стандартной библиотеки, хорошо для меня.

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


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

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

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


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

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


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


поскольку параметры передаются по значению, не имеет значения, указываете ли вы const или нет с точки зрения вызывающей функции.В принципе, нет смысла объявлять параметры pass by value как 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) ссылка была бы более уместной здесь и дает вам такое же поведение. Ну, ладно. Просто картина отличается от той, что я видел в других местах...


быть VB.NET программист, который должен использовать программу на C++ с 50 + открытыми функциями и a .H файл, который спорадически использует квалификатор const, трудно узнать, когда получить доступ к переменной с помощью ByRef или ByVal.

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

Итак, теперь у меня есть дельце, пытаясь убедить разработчик, что они должны действительно определить свои переменные (в .H файл) таким образом, что позволяет автоматизированный метод создания всех VB.NET определения функций легко. Тогда они самодовольно скажут: "читай... документация."

Я написал сценарий awk, который анализирует a .H файл, и создает все команды функции объявления, но без индикатора относительно того, какие переменные R / O vs R/ W, он делает только половину работа.

EDIT:

по рекомендации другого пользователя я добавляю следующее:

вот пример плохо сформированного (IMO).ч запись;

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