Использование const для параметров функции - PullRequest
347 голосов
/ 23 сентября 2008

Как далеко вы идете с const? Вы просто делаете функции const, когда это необходимо, или вы используете всю свинью и используете ее везде? Например, представьте себе простой мутатор, который принимает один логический параметр:

void SetValue(const bool b) { my_val_ = b; }

Это const действительно полезно? Лично я предпочитаю использовать его широко, включая параметры, но в этом случае мне интересно, стоит ли это делать?

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

.h file

void func(int n, long l);

.cpp file

void func(const int n, const long l)

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

Ответы [ 30 ]

383 голосов
/ 23 сентября 2008

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

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

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

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

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

166 голосов
/ 23 сентября 2008

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

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

137 голосов
/ 23 сентября 2008

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

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

75 голосов
/ 23 сентября 2008

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

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

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

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

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

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

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

65 голосов
/ 14 июня 2012

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

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

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

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

35 голосов
/ 23 сентября 2008

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

int i = 5 ; // i is a constant

var int i = 5 ; // i is a real variable
25 голосов
/ 23 сентября 2008

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

foo() = 42

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

foo() == 42

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

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

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

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

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

14 голосов
/ 05 мая 2009

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

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

9 голосов
/ 14 ноября 2008

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

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

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

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

7 голосов
/ 23 сентября 2008

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

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

Функция подписи для

void foo(int a);

и

void foo(const int a);

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

Асаф

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