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

StackOverflow https://stackoverflow.com/questions/117293

  •  02-07-2019
  •  | 
  •  

Вопрос

Как далеко ты зайдёшь с 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, за исключением параметров ссылки и указателя.Для скопированных объектов это не имеет особого значения, хотя это может быть безопаснее, поскольку сигнализирует о намерении внутри функции.Это действительно приговор.Однако я склонен использовать const_iterator при циклическом выполнении чего-либо и не собираюсь его изменять, поэтому я думаю, что каждому свое, при условии, что корректность const для ссылочных типов строго поддерживается.

Другие советы

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

Неправильный.

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

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

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

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

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

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

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

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

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

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

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

*:Если только это не отбросит константность, но это уже другой пост.

Дополнительные лишние константы плохи с точки зрения API:

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

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

Аргумент «доведение до абсурда» для дополнительных констант в 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 может заставить вас подумать, что это не изменит память в переданном буфере данных - однако это не так! Излишняя константа может привести к опасным и неправильным предположениям о вашем 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 только в определении.

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

Однако верно и обратное...вы можете поместить ложную константу только в объявление и игнорировать ее в определении.Это только делает лишнюю константу в 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
}

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

Посмотрите на этот пример.Что более читабельно?Очевидно ли, что единственная причина использования дополнительной переменной во второй функции заключается в том, что какой-то разработчик 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 должен был быть значением по умолчанию в C++.Так :

int i = 5 ; // i is a constant

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

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

foo() = 42

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

foo() == 42

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

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

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

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

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

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

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

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

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

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

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

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

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

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

void foo(int a);

и

void foo(const int a);

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

Асаф

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

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

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

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

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

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

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

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

Маркировка значений параметров «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 как способ предотвратить случайное изменение наших переменных внутри функций (которые мы не должны делать/только для чтения).поэтому, если мы случайно сделали это во время компиляции, компилятор сообщит нам об этом.это особенно важно, если вы не единственный, кто работает над этим проектом.

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

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

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

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

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

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

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

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

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

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

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

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

Обобщить:

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

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

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

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

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

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

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

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

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

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

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

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

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

Мне кажется, что мы все еще слишком ограничены мышлением в стиле 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;

}

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

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

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

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

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

РЕДАКТИРОВАТЬ:

По просьбе другого пользователя я добавляю следующее:

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

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top