Константа до или константа после?

152

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

const Object* obj; // can't change data
Object* const obj; // can't change pointer
const Object* const obj; // can't change data or pointer

Однако вы также можете использовать синтаксис:

Object const *obj; // same as const Object* obj;

Единственное, что кажется важным, - это то, с какой стороны звездочки вы помещаете constключевое слово. Лично я предпочитаю ставить constслева от типа, чтобы указать, что данные не подлежат изменению, поскольку я считаю, что он лучше читается в моем мышлении слева направо, но какой синтаксис появился первым?

Что еще более важно, почему существует два правильных способа указания constданных и в какой ситуации вы бы предпочли или понадобились бы один другому, если таковой имеется?

Редактировать:

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

Полагаю, так было и в C?

AJG85
источник
10
В макросах всегда добавляйте const после типа, например, #define MAKE_CONST(T) T constвместо, #define MAKE_CONST(T) const Tчтобы MAKE_CONST(int *)правильно расширить до int * constвместо const int *.
jotik
7
Я видел эти два стиля, которые называются «восточная константа» и «западная константа».
Том Андерсон
16
@TomAnderson, но на самом деле это должно быть "east const" и "const west".
YSC

Ответы:

104

почему существует два правильных способа указания constданных и в какой ситуации вы бы предпочли или понадобились бы один другому, если таковой имеется?

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

Причина, по которой они определили грамматику таким образом, заключалась в том, что их компилятор C анализировал ввод слева направо и завершал обработку каждого токена по мере его использования. Использование *токена изменяет состояние текущего объявления на тип указателя. Встреча constпосле *означает, что constквалификатор применяется к объявлению указателя; обнаружение его до того, *как квалификатор применяется к указанным данным.

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

Аналогичный случай возникает при объявлении указателей на функции, где:

  • void * function1(void)объявляет функцию, которая возвращает void *,

  • void (* function2)(void)объявляет указатель функции на возвращаемую функцию void.

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

Хит Ханникатт
источник
7
Керниган был соавтором книги, но не участвовал в разработке C, только Ричи.
Tom Zych
10
Я никогда не мог вспомнить, какой из них какой. Благодаря вашему объяснению у меня наконец появилась мнемотехника, чтобы это запомнить. Благодарность! До того, как *синтаксический анализатор компилятора не узнает, что это указатель, он является константой для значения данных. После * это относится к постоянному указателю. Блестяще. И, наконец, это объясняет, почему я умею так const charже хорошо char const.
Вит Бернатик
2
Предположение о том, почему это было сделано таким образом, мне кажется довольно слабым / противоречивым. То есть, если бы я определял язык и писал компилятор, и я хотел, чтобы он оставался простым и «разбирал ввод слева направо и завершал обработку каждого токена по мере его потребления», как вы говорите, мне кажется Я бы потребовал, constчтобы объект всегда шел после того, что он квалифицирует ... именно так, чтобы я всегда мог завершить обработку константы сразу после ее использования. Так что, похоже, это аргумент в пользу запрета west-const, а не разрешения.
Don Hatch
3
«Поскольку семантическое значение не меняется, если квалификатор const появляется до или после спецификаторов типа, он принимается в любом случае». Разве это не круговое рассуждение? Вопрос в том, почему семантическое значение определяется так, поэтому я не думаю, что это предложение что-то дает.
Don Hatch
1
@donhatch Вы должны помнить, что относительно сегодняшнего дня и предположений, которые мы делаем, основываясь на нашем знакомстве с хорошим дизайном языков программирования, языки тогда были довольно новым явлением. Кроме того, субъективное суждение - разрешительный или ограниченный язык. Например, должен ли Python иметь ++оператор? «Приговор», imho, помог мне понять, что не было никакой особой причины, кроме того, что они могли. Может быть, сегодня они сделают другой выбор, а может, и нет.
ragerdl 08
78

Правило такое:

const применяется к тому, что осталось от него. Если слева ничего нет, то это относится к тому, что находится справа от него.

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

Но я считаю, что это очень субъективная точка зрения.

Horstforst
источник
19
Я предпочитаю ставить его слева, но думаю, что размещение справа имеет больше смысла. Обычно типы в C ++ читаются справа налево, например Object const *, это указатель на объект const. Если вы поместите constслева, он будет считаться указателем на объект, который является const, что на самом деле не очень хорошо работает.
Collin Dauphinee
1
У меня сложилось впечатление, что слева - согласованность в человеческом стиле с другими типами объявлений C (с компьютерной точки зрения это неверно, поскольку constэто не класс хранилища, но люди не парсеры).
geekosaur
1
@Heath Я считаю, что это скорее рекомендация, чем правило, и я часто слышал это как способ вспомнить, как компилятор интерпретирует это ... Я понимаю, как это работает, поэтому мне было любопытно только о мыслительном процессе, стоящем за решение поддержать его в обоих направлениях.
AJG85
3
@HeathHunnicutt правило существует, но оно немного сложнее: c-faq.com/decl/spiral.anderson.html
imallett
2
@HeathHunnicutt: правило спирали - это расширенная версия первого комментария комментатора «Обычно вы читаете типы в [C /] C ++ справа налево». Я предположил, что вы этому противоречите. Однако я думаю, что вместо этого вы имели в виду сам ответ.
imallett
67

Я предпочитаю второй синтаксис. Это помогает мне отслеживать "что" постоянно, читая объявление типа справа налево:

Object * const obj;        // read right-to-left:  const pointer to Object
Object const * obj;        // read right-to-left:  pointer to const Object
Object const * const obj;  // read right-to-left:  const pointer to const Object
Мэтт Дэвис
источник
3
В яблочко. «Постоянный указатель на постоянный объект» - Object const* constнет const const Object*. «const» не может быть слева, за исключением особого случая, когда это очень нравится многим людям. (См. Heath выше.)
cdunn2001
39

Порядок ключевых слов в объявлении не так уж и фиксирован. Есть много альтернатив «единому истинному порядку». Как это

int long const long unsigned volatile i = 0;

или должно быть

volatile unsigned long long int const i = 0;

??

Бо Перссон
источник
26
+1 за совершенно запутанное определение простой переменной. :)
Xeo
@rubenvb - Да, к сожалению, есть. Грамматика просто говорит, что a decl-specifier-seq- это последовательность decl-specifiers. В грамматике нет порядка, и количество вхождений для каждого ключевого слова ограничено только некоторыми семантическими правилами (у вас может быть одно, constно два long:-)
Бо Перссон
3
@rubenvb - Да, unsignedэто тип, такой же, как unsigned intи int unsigned. unsigned longэто другой тип, такой же, как unsigned long intи int long unsigned. Видите узор?
Бо Перссон
2
@Bo: Я вижу беспорядок, нужно три, чтобы увидеть закономерность ;). ОК, спасибо
rubenvb
1
Раньше у вас была возможность добавить staticк беспорядку слов, но только недавно компиляторы жаловались, что staticнужно идти первым.
Марк Лаката 05
8

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

typedef int* IntPtr;
const IntPtr p1;   // same as int* const p1;

Если ваш стандарт кодирования допускает typedef указателей, то он действительно должен настаивать на том, чтобы после типа помещалась константа. В любом случае, кроме применения к типу, константа должна следовать за тем, к чему она применяется, поэтому согласованность также свидетельствует в пользу константы после. Но местные правила кодирования важнее всего этого; разница обычно не настолько важна, чтобы вернуться и изменить весь существующий код.

Джеймс Канце
источник
1
Я думаю, это может выявить причину, по которой у нас нет определений типов указателей в наших довольно слабо определенных стандартах в этом магазине.
AJG85
2
Моя политика (когда я принимаю решение в одиночку) - ставить константу после (для согласованности) и не использовать typedef для указателей (или typedefs в целом) :-). И, кстати, string :: iterator vs. string :: const_iterator, вероятно, также должны быть учтены в вашем решении. (Чтобы запутать :-). Нет правильного ответа.)
Джеймс Канце
Ах да, я мог бы включить поведение const std::string::const_iteratorтакже для хорошей меры;)
AJG85
2
@JamesKanze - Погодите, помогите мне ... Я не вижу путаницы в опубликованном примере. Что еще может const IntPtr p1означать, кроме «постоянный указатель на целое число» (т.е. «постоянный указатель на целое число»)? Никто в здравом уме, даже не зная, как IntPtrопределяется, не мог бы подумать, что p1это изменчиво. И в этом отношении, почему кто-то может ошибочно предполагать, что *p1это неизменяемое? Более того, размещение константы в другом месте (например, IntPtr const p1) вообще не меняет семантику.
Тодд Леман,
3
@ToddLehman Возможно, вы не заметите путаницы, но большинство программистов на C ++ ее понимают и систематически ошибаются (без сомнения, этому помогают такие вещи, как std::vector<T>::const_iterator, где константен не итератор, а то, на что он указывает).
Джеймс Канце
8

Есть исторические причины, по которым приемлемо либо левое, либо правое. Страуструп добавил const в C ++ к 1983 году , но до C89 / C90 он не попал в C.

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

int getInt() const;
Ник Вестгейт
источник
1
... ну, "веская причина" не так уж и убедительна, потому что другие возможные места для "const" не будут означать то же самое. const int& getInt(); int& const getInt();
Maestro
1
@Maestro: я предлагаю int const& getInt();лучше, чем эквивалент, const int& getInt();тогда как int& const getInt();то, с чем вы его сравниваете, является избыточным (ссылки уже являются константными), хотя законно и обычно дает предупреждение. В любом случае, const в функции-члене изменяет thisуказатель в функции с Foo* constна Foo const* const.
Ник Вестгейт
constв функции-члене означает совсем не одно и то же - или это void set(int)&;какая-то ссылка на функцию?
Дэвис Херринг
0

C использует синтаксис с написанием справа налево. Просто прочтите объявления справа налево:

int var = 0;

// one is a pointer to a const int
int const * one = &var; 
// two is a pointer to an int const (same as "const int")
const int * two = &var; 

// three is a constant pointer to an int
int * const three = &var;

Это влияет на первое, что осталось от "const".

Чтобы получить больше удовольствия, прочтите это руководство: http://cseweb.ucsd.edu/~ricko/rt_lt.rule.html

MacMark
источник