Використання 'const' для параметрів функції


396

Як далеко ви їдете 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.
selwyn

10
Я згоден. :-) (З питанням, не останнім коментарем!) Якщо значення не слід змінювати в тілі функції, це може допомогти зупинити дурні == або = помилки, ви ніколи не повинні ставити const в обох, (якщо це передається за значенням, ви повинні інакше) Це недостатньо серйозно, щоб вступати в аргументи з цього приводу!
Кріс Хуанг-Лівер

19
@selwyn: Хоча, якщо ви передаєте const int функції, проте, вона буде скопійована (оскільки це не посилання), і тому const-ness не має значення.
jalf

1
У цьому питанні відбувається та сама дискусія: stackoverflow.com/questions/1554750/…
Часткова

5
Я усвідомлюю, що цій посаді є пару років, але, як новий програміст, мені було цікаво саме це питання, і я натрапив на цю розмову. На мою думку, якщо функція не повинна змінювати значення, будь то посилання чи копія значення / об'єкта, воно повинно бути const. Це безпечніше, це самодокументація та більш налагоджено. Навіть для найпростішої функції, яка має одне твердження, я все ж використовую const.

Відповіді:


187

Причина полягає в тому, що const для параметра застосовується лише локально в межах функції, оскільки він працює над копією даних. Це означає, що підпис функції дійсно все одно однаковий. Це, мабуть, поганий стиль робити це дуже багато.

Я особисто схильний не використовувати const, крім параметрів посилання та вказівника. Для скопійованих об'єктів це насправді не має значення, хоча це може бути безпечніше, оскільки воно сигналізує про наміри в межах функції. Це дійсно судовий заклик. Я, як правило, використовую const_iterator, хоча під час циклу на щось, і я не збираюсь його змінювати, тому я думаю, що кожен його власний, якщо правильність const для референтних типів суворо зберігається.


57
Я не можу погодитися з частиною "поганого стилю". Відмова constвід прототипів функцій має ту перевагу, що вам не потрібно змінювати файл заголовка, якщо ви вирішите constпізніше піти з частини реалізації.
Michał Górny

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 { }має сенс тут. (Sidenote: і абонент, і імплементація дбають про те, щоб функція не змінювала свого Fooоб'єкта, таким чином, const в кінці декларації)
CharonX,

415

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

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

Йдеться про самодокументацію коду та своїх припущень.

Якщо у вашому коді працює багато людей, а ваші функції нетривіальні, слід позначити "const" будь-яким і всім, що ви можете. Коли ви пишете індустріальний код, завжди слід вважати, що ваші колеги - це психопати, які намагаються отримати вас будь-яким способом (тим більше, що це часто ви самі в майбутньому).

Крім того, як хтось згадував раніше, це може допомогти компілятору дещо оптимізувати речі (хоча це і є довгий кадр).


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

19
Я проголосував за це. Я думаю, що ти розбавляєш те, що намагаєшся вказати const, застосовуючи це до простих аргументів значення.
тоніло

26
Я проголосував за це. Оголошення параметра "const" додає параметр семантичної інформації. Вони підкреслюють те, що мав намір оригінальний автор коду, і це сприятиме підтримці коду з часом.
Річард Корден

13
@tonylo: ти неправильно розумієш. Йдеться про позначення локальної змінної як const всередині блоку коду (це буває функцією). Я б зробив те ж саме для будь-якої локальної змінної. Ортогональним є наявність API, який є коректним для const, що дійсно також важливо.
rlerallut

56
І він може вловлювати помилки всередині функції - якщо ви знаєте, що параметр не слід змінювати, то його оголошення const означає, що компілятор скаже вам, якщо ви випадково його зміните.
Адріан

156

Іноді (занадто часто!) Мені доводиться розплутувати чужий код C ++. І всі ми знаємо, що чужий код C ++ - це повний безлад майже за визначенням :) Отже, перше, що я роблю, щоб розшифрувати локальний потік даних, - це const у кожному визначенні змінної, поки компілятор не почне бракувати. Це означає також аргументи, що визначають значення const, оскільки вони є просто фантазійними локальними змінними, ініціалізованими викликом.

Ах, я хотів би, щоб змінні були const за замовчуванням, а змінні для non-const змінних були потрібні :)


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

8
@ysap, 1. Позначення const якомога більше дозволяє мені побачити, які частини рухаються, а які ні. На мій досвід, багато місцевих жителів фактично const, а не навпаки. 2. "змінна Const" / "незмінна змінна" може звучати як оксиморон, але це стандартна практика у функціональних мовах, а також у деяких нефункціональних; див. Іржа, наприклад: doc.rust-lang.org/book/variable-bindings.html
Костянтин

1
Також стандартно зараз за деяких обставин у c ++; наприклад, лямбда [x](){return ++x;}є помилкою; дивіться тут
anatolyg

10
constЗа замовчуванням " Rust :) " "за замовчуванням є" "
Phoenix

Змінні не обов'язково повинні бути присвоєними, щоб вони могли змінюватись; значення, з яким вони ініціалізовані, також можуть змінюватися під час виконання.
Сфінкс

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 не змінює * вміст цього параметра." Передача посилання на const також дозволяє компілятору приймати певні рішення про ефективність.

*: Якщо тільки це не позбавить констрес, але це вже інший пост.


3
Це не те, про що йдеться в цьому питанні; Звичайно, для посилаються або вказують на аргументи, це гарна ідея використовувати const (якщо посилане або вказане значення не змінюється). Зверніть увагу, що це не параметр, який є const у вашому прикладі вказівника; це річ, на яку вказує параметр.
tml

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

73

Зайві зайві показники погані з точки зору API:

Внесення зайвих зайвих const у свій код для внутрішніх параметрів типу, переданих значенням, захаращує ваш API , не даючи жодних значущих обіцянок абоненту або користувачеві API (це лише перешкоджає реалізації).

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

Аргумент "reductio ad absurdum" для додаткових consts в API хороший для цих перших двох пунктів. Це якщо б більше параметрів const були хорошими, то кожен аргумент, який може мати на ньому const, повинен бути const на ньому. Насправді, якби це було дійсно так добре, ви хочете, щоб const був типовим для параметрів, а ключове слово типу "mutable" було б лише тоді, коли ви хочете змінити параметр.

Тож давайте спробуємо ввести в дію будь-коли:

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

Подивіться, я так чи інакше реалізував це, хоча пообіцяв цього не робити - просто використовуючи функцію обгортки. Це як коли поганий хлопець обіцяє не вбивати когось у кіно і наказує своєму прихильникові вбити їх замість.

Ці зайві конкурси варті не більше, ніж обіцянки з фільму-поганого хлопця.


Але здатність брехати стає ще гіршою:

Я просвітився, що ви можете невідповідно const у заголовку (декларації) та коді (визначенні), використовуючи помилкові const. Захисні адвокати твердять, що це хороша річ, оскільки вона дозволяє встановлювати const лише у визначенні.

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

Однак зворотне вірно ... ви можете поставити помилковий const тільки в декларації і проігнорувати його у визначенні. Це лише робить зайвий 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;
    }
}

Сподіваємось, ми щось тут дізналися. Зайвий конст - це перенапружений очей, роздратований API, дратівливий гнів, неглибока і безглузда обіцянка, непотрібна перешкода, а іноді призводить до дуже небезпечних помилок.


9
Чому голоси? Це набагато корисніше, якщо ви залишите короткий коментар на голосному коментарі.
Адісак

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

23
@Adisak Я не бачу нічого поганого у вашій відповіді, але, як ви бачите, ви бачите, що ви пропускаєте важливий момент. Визначення / реалізація функції не є частиною API, а лише оголошенням функції . Як ви вже говорили, оголошення функцій з параметрами const є безглуздим і додає безладу. Однак користувачам API ніколи не доведеться бачити його реалізацію. Тим часом реалізатор може вирішити const-кваліфікувати деякі параметри у визначенні функції ТІЛЬКИ для ясності, що цілком добре.
jw013

17
@ jw013 є правильним void foo(int)і void foo(const int)точно такою ж функцією, а не перевантаження. ideone.com/npN4W4 ideone.com/tZav9R Констанція тут - це лише детальна інформація про функціональне тіло і не впливає на дозвіл на перевантаження. Залишайте 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 () визначено для повернення посилання, що не стосується const:

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

У цій старій статті "Гуру тижня" про компанію.lang.c ++. Тут модерується хороша дискусія .

Відповідна стаття GOTW розміщена на веб-сайті Herb Sutter тут .


1
Герб Саттер - справді розумний хлопець :-) Однозначно варто прочитати, і я погоджуюся з ВСІМ його пунктами.
Адісак

2
Добре стаття, але я не згоден з ним щодо аргументів. Я змушую їх і const тому, що вони схожі на змінні, і я ніколи не хочу, щоб хтось міняв мої аргументи.
QBziZ

9

Я кажу, що ваші значення параметрів.

Розглянемо цю функцію баггі:

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

Якщо параметр числа був const, компілятор зупинився і попередив би нас про помилку.


2
інший спосіб - використання if (0 == число) ... else ...;
Йоханнес Шауб - ліб

5
@ ChrisHuang-Лівер Жахливе це не так , якщо говорити як Yoda ви робите: stackoverflow.com/a/2430307/210916
MPelletier

GCC / Clang -Wall дає вам -Wparentheses, який вимагає зробити це "if ((число = 0))", якщо це дійсно те, що ви мали намір зробити. Що добре працює як заміна Yoda.
Jetski S-type

8

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

Внесення const до параметра boolean 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

Ах, жорсткий. З одного боку, декларація - це договір, і справді не має сенсу передавати аргумент const за значенням. З іншого боку, якщо ви подивитесь на реалізацію функції, ви даєте компілятору більше шансів на оптимізацію, якщо ви оголосите аргумент постійним.


5

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

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

Нарешті, функція, яка не змінює поточний об'єкт (це), може, і, ймовірно, повинна бути оголошена const. Приклад нижче:

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

Це обіцянка не змінювати об'єкт, до якого застосовується цей виклик. Іншими словами, ви можете зателефонувати:

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

Якщо функція не була const, це призведе до попередження компілятора.


5

Позначення параметрів значення "const", безумовно, суб'єктивна річ.

Однак я фактично вважаю за краще позначити параметри значення const, як у вашому прикладі.

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

Для мене це значення чітко вказує на те, що значення параметрів функції ніколи не змінюються функцією. Вони будуть мати те саме значення на початку, що і в кінці. Для мене це частина дотримання дуже функціонального стилю програмування.

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

Однак для більшої функції її форма документації на реалізацію і її виконує компілятор.

Я можу бути впевнений, що якщо я роблю деякі обчислення з 'n' та 'l', я можу переробляти / переміщувати ці обчислення, не боячись отримати інший результат, тому що я пропустив місце, де змінюється одна чи обидві.

Оскільки це детальна інформація про реалізацію, вам не потрібно оголошувати параметри значення const у заголовку, як і вам не потрібно оголошувати параметри функції з тими самими іменами, які використовує реалізація.


4

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


3

Я схильний використовувати const, коли це можливо. (Або інше відповідне ключове слово для цільової мови.) Я роблю це виключно тому, що дозволяє компілятору зробити додаткові оптимізації, які він не зміг би зробити інакше. Оскільки я поняття не маю, якими можуть бути ці оптимізації, я завжди це роблю, навіть там, де це здається дурним.

Наскільки я знаю, компілятор може дуже добре побачити параметр значення const і сказати: "Ей, ця функція жодним чином не змінює її, тому я можу пройти посилання та зберегти кілька циклів годин". Я не думаю, що це колись зробить таке, оскільки це змінює підпис функції, але це робить сенс. Можливо, це робить якусь різну маніпуляцію стеком чи щось таке ... Справа в тому, я не знаю, але я знаю, що намагатися бути розумнішим, ніж компілятор, що призводить до того, що мене збентежують.

C ++ має додатковий багаж з ідеєю коректності, тому він стає ще важливішим.


Хоча це може допомогти в деяких випадках, я підозрюю, що можливість сприяння оптимізації різко завищена як користь const. Швидше, справа в тому, щоб заявити про намір в рамках реалізації та пізніше пізніше (випадково наростивши неправильну локальну змінну, оскільки її не було const). Паралельно я також додам, що компілятори дуже раді змінити підписи функцій, в тому сенсі, що функції можуть бути вбудовані, і щойно вбудовані весь спосіб їх роботи можна змінити; додавання або видалення посилань, створення "змінних" літералів тощо - це все як правило, як якщо б
underscore_d

3

1. Найкраща відповідь на основі моєї оцінки:

Відповідь @Adisak - найкраща відповідь тут на основі моєї оцінки. Зауважте, що ця відповідь є частково найкращою, оскільки вона також є найбільш добре підкріпленою реальними прикладами коду , крім використання звуку та продуманої логіки.

2. Мої власні слова (згодні з найкращою відповіддю):

  1. Для додаткової вартості додавання користі немає const. Все, що вона робить:
    1. обмежте, що реалізатор повинен робити копію кожного разу, коли вони хочуть змінити параметр введення у вихідному коді (яка зміна не матиме жодних побічних ефектів, оскільки те, що передано, - це вже копія, оскільки вона проходить через значення). І часто для зміни функції використовується параметр введення, переданий за значенням, тому додавання constскрізь може перешкоджати цьому.
    2. і додавання constнадмірно захаращує код constскрізь, відволікаючи увагу від 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 у деклараціях про функції" , а також є корисним читанням. Він є більш інформативним і менш доказовим щодо того, що робити, і виходячи з контексту, перш ніж було зроблено перед правилом Google C ++ Style Guide за constцитованим трохи вище, але внаслідок ясності, яку він надав, constдо Google C ++ було додано правило, цитоване трохи вище. Посібник зі стилів.

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

4. Лінійка Кланг clang-tidy, має деякі варіанти цього:

A. Крім того , варто відзначити , що Лінтера дзвону, в clang-tidy, має варіант, readability-avoid-const-params-in-decls, описані тут , щоб підтримати виконання в кодової базі НЕ використовується constдля параметрів функції pass-by-value :

Перевіряє, чи є в оголошенні функції параметри, які є найвищим рівнем 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]

B. Він також має такий варіант: readability-const-return-type- https://clang.llvm.org/extra/clang-tidy/checks/readability-const-return-type.html

5. Мій прагматичний підхід до того, як я б написав посібник зі стилю з цього питання:

Я просто скопіюйте та вставте це у свій посібник зі стилів:

[КОПІЯ / ПАСТИЧНИЙ СТАРТ]

  1. Завжди використовуйте const параметри функції, передані посиланням або покажчиком, коли їх вміст (на що вони вказують) призначений НЕ змінювати. Таким чином, стає очевидним, коли змінна, передана посиланням або IS вказівника, очікується на зміну, оскільки її буде бракувати 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 для функції оголошує, що функція не повинна змінювати членів класів.


2

Узагальнити:

  • "Зазвичай вартість прохідної вартості в кращому випадку не є корисною та оманливою." Від GOTW006
  • Але ви можете додати їх у .cpp так, як це зробите зі змінними.
  • Зауважте, що стандартна бібліотека не використовує const. Напр std::vector::at(size_type pos). Що достатньо для стандартної бібліотеки, це добре для мене.

2
"Те, що достатньо для стандартної бібліотеки, добре для мене", не завжди відповідає дійсності. Наприклад, стандартна бібліотека використовує потворні імена змінних, як і _Tmpвесь час - цього ви не хочете (насправді вам заборонено їх використовувати).
anatolyg

1
@anatolyg - це деталізація реалізації
Фернандо Пелліціоні

2
Гаразд, імена змінних, і типи, визначені const у списках аргументів, є деталями реалізації. Що я хочу сказати, це те, що впровадження стандартної бібліотеки іноді не добре. Іноді, можна (і слід) зробити краще. Коли був написаний код для стандартної бібліотеки - 10 років тому? 5 років тому (деякі новітні його частини)? Ми можемо написати кращий код сьогодні.
anatolyg

1

Якщо параметр передається за значенням (і не є посиланням), зазвичай велика різниця є в тому, чи параметр оголошений як const чи ні (якщо він не містить посилання-член - не проблема для вбудованих типів). Якщо параметр є посиланням або вказівником, зазвичай краще захистити згадувану / вказівну пам’ять, а не сам вказівник (я думаю, ви не можете зробити посилання самою const, не те, що це важливо, оскільки ви не можете змінити арбітра) . Здається, добре захистити все, що ви можете, як const. Ви можете опустити це, не побоюючись помилитися, якщо параметри є лише POD-кодами (включаючи вбудовані типи) і немає шансів їх подальшого зміни по дорозі (наприклад, у вашому прикладі параметр bool).

Я не знав про різницю декларації файлів у форматі .h / .cpp, але це має певний сенс. На рівні машинного коду нічого не є "const", тому якщо ви оголошуєте функцію (в .h) як non-const, код такий же, як якщо ви оголошуєте її як const (оптимізацію вбік). Однак це допоможе вам зарахувати компілятор, що ви не зміните значення змінної всередині реалізації функції (.ccp). Це може стати в нагоді у випадку, коли ви успадковуєте інтерфейс, який дозволяє змінити, але вам не потрібно змінювати параметр, щоб досягти необхідної функціональності.


0

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


4
іноді ви хочете передати об'єкт за посиланням (з міркувань продуктивності), але не змінювати його, тому const є обов'язковим. Збереження всіх таких параметрів - навіть булів - const було б тоді хорошою практикою, полегшуючи читання коду.
gbjbaanb

0

що потрібно пам’ятати з const, що набагато простіше зробити речі const з самого початку, ніж спробувати вкласти їх згодом.

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

Я б більше схильний пропустити ключове слово const у файлі cpp, ніж у заголовку, але оскільки я схильний вирізати + вставити їх, вони зберігатимуться в обох місцях. Я поняття не маю, чому компілятор дозволяє це, я думаю, що це компілятор. Найкраща практика - це, безумовно, розмістити ключове слово const в обох файлах.


Я взагалі цього не розумію. Чому ви схильні опускати його у файлі cpp (визначення функції)? Ось де це насправді щось означає і може вловлювати помилки. Чому , на вашу думку, найкращою практикою вважати const в обох місцях? У файлі заголовка (декларація функції) це нічого не означає і захаращує API. Можливо, є невелике значення того, щоб значення decl і defn виглядало точно так само, але мені здається, це дійсно незначна користь порівняно з проблемою захаращення API.
Дон Хетч

@DonHatch 8 років потому, ух. У будь-якому випадку, як сказав ОП, "я також був здивований, дізнавшись, що ви можете опустити const з параметрів у декларації функції, але можете включити її у визначення функції".
gbjbaanb

0

Дійсно немає причин робити параметр value "const", оскільки функція може змінити копію змінної.

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


0

Параметр Const корисний лише тоді, коли параметр передається за посиланням, тобто або посиланням або вказівником. Коли компілятор бачить параметр const, переконайтеся, що змінна, використана в параметрі, не змінюється в тілі функції. Чому хтось хотів би зробити параметр за значенням як постійний? :-)


З багатьох причин. Визначення параметра за значенням constчітко визначає: "Мені це не потрібно змінювати, тому я заявляю про це. Якщо я спробую змінити його пізніше, дайте мені помилку часу компіляції, щоб я міг або виправити помилку, або зняти позначку як const". Тож це питання і гігієни коду, і безпеки. До всього, що потрібно для додавання до файлів реалізації, це має бути щось, що люди роблять як чистий рефлекс, IMO.
підкреслюйте_d

0

Оскільки параметри передаються за значенням, це не має ніякої різниці, якщо ви вказали const чи ні з точки зору викликової функції. В основному не має сенсу оголошувати параметри значення pass як const.


0

Усі судження у ваших прикладах не мають мети. C ++ за замовчуванням пропускається за значенням, тому функція отримує копії цих int та booleans. Навіть якщо функція не змінює їх, копія абонента не впливає.

Тож я б уникав зайвих змагань, оскільки

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

-1

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

Мені здається, ми все ще занадто обмежені способом мислення в стилі С. У парадигмі ООП ми розігруємо об'єкти, а не типи. Об'єкт Const може концептуально відрізнятися від об'єкта non-const, зокрема в сенсі логіка-const (на відміну від побітових-const). Таким чином, навіть якщо коректність правильності функціональних парам є (можливо) надмірною обережністю у випадку ПОД, це не так у випадку з об'єктами. Якщо функція працює з об'єктом 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;

}

пс .: Ви можете стверджувати, що (const) посилання тут було б більш доречним і надає вам таку саму поведінку. Ну правильно. Просто даю іншу картину від тієї, яку я міг бачити в іншому місці ...

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.