Const antes ou depois da const?

145

Para começar, você provavelmente sabe que constpode ser usado para tornar os dados de um objeto ou um ponteiro não modificáveis ​​ou ambos.

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

No entanto, você também pode usar a sintaxe:

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

A única coisa que parece importar é de que lado do asterisco você colocou a constpalavra - chave. Pessoalmente, prefiro colocar constà esquerda do tipo para especificar que os dados não são modificáveis, pois acho que eles são melhores na minha mentalidade da esquerda para a direita, mas qual sintaxe veio primeiro?

Mais importante ainda, por que existem duas maneiras corretas de especificar constdados e em que situação você preferiria ou precisaria uma sobre a outra, se houver?

Editar:

Parece que essa foi uma decisão arbitrária quando o padrão de como os compiladores deveriam interpretar as coisas foi elaborado muito antes de eu nascer. Como consté aplicado ao que fica à esquerda da palavra-chave (por padrão?), Acho que eles descobriram que não havia mal em adicionar "atalhos" para aplicar palavras-chave e digitar qualificadores de outras maneiras pelo menos até que a declaração seja alterada por analisando um * ou & ...

Este foi o caso em C também, então eu estou assumindo?

AJG85
fonte
9
Nas macros, adicione sempre const após o tipo, por exemplo, em #define MAKE_CONST(T) T constvez de, #define MAKE_CONST(T) const Tpara MAKE_CONST(int *)expandir corretamente para em int * constvez de const int *.
jotik
6
Eu já vi esses dois estilos chamados "const leste" e "const oeste".
Tom Anderson
13
@ TomAnderson, mas realmente deve ser "const const" e "const west".
YSC

Respostas:

96

por que existem duas maneiras corretas de especificar constdados e em que situação você preferiria ou precisaria uma sobre a outra, se houver?

Essencialmente, a razão pela qual a posição dos constespecificadores anteriores a um asterisco não importa é que a gramática C foi definida dessa maneira por Kernighan e Ritchie.

A razão pela qual eles definiram a gramática dessa maneira era provável que seu compilador C analisasse a entrada da esquerda para a direita e terminasse de processar cada token à medida que o consumia. Consumir o *token altera o estado da declaração atual para um tipo de ponteiro. Encontrar constdepois *significa que o constqualificador é aplicado a uma declaração de ponteiro; encontrá-lo antes dos *meios em que o qualificador é aplicado aos dados apontados.

Como o significado semântico não muda se o constqualificador aparecer antes ou depois dos especificadores de tipo, ele é aceito de qualquer maneira.

Um tipo semelhante de caso surge ao declarar ponteiros de função, em que:

  • void * function1(void)declara uma função que retorna void *,

  • void (* function2)(void)declara um ponteiro de função para uma função que retorna void.

Novamente, o que se deve notar é que a sintaxe do idioma suporta um analisador da esquerda para a direita.

Heath Hunnicutt
fonte
7
Kernighan foi co-autor do livro, mas não estava envolvido no design de C, apenas Ritchie.
Tom Zych #
8
Eu nunca conseguia lembrar qual é qual. Graças à sua explicação, finalmente tenho o mnemotécnico para lembrar. Obrigado! Antes que o *analisador do compilador não saiba que é um ponteiro, é uma constante para o valor dos dados. Depois de *, ele está relacionado ao ponteiro constante. Brilhante. E, finalmente, explica por que posso fazer const chartão bem quanto char const.
Vit Bernatik
2
O palpite de por que foi feito dessa maneira me parece um pouco fraco / autocontraditório. Ou seja, se eu estivesse definindo um idioma e escrevendo um compilador, e quisesse mantê-lo simples e "analisar a entrada da esquerda para a direita e concluir o processamento de cada token à medida que o consome", como você diz, parece-me Eu exigiria constque sempre viesse depois da coisa qualificada ... exatamente para que eu pudesse sempre terminar o processamento da const imediatamente após consumi-la. Portanto, este parece ser um argumento para proibir a const-oeste, em vez de permitir.
Don Hatch
3
"Como o significado semântico não muda se o qualificador const aparecer antes ou depois dos especificadores de tipo, é aceito de qualquer maneira." Não é esse raciocínio circular? A questão é por que o significado semântico é definido assim, então não acho que essa frase contribua com nada.
Don Hatch
1
@donhatch Você deve se lembrar que, em relação a hoje e às suposições que fazemos com base em nossa familiaridade com o bom design de linguagem de programação, as linguagens eram coisas muito novas na época. Além disso, se alguém tem uma linguagem permissiva ou restrita é um julgamento de valor. Por exemplo, python deve ter um ++operador? "A frase", imho, me ajudou a perceber que não havia outra razão em particular senão porque eles podiam. Talvez eles fizessem uma escolha diferente hoje / talvez não.
ragerdl
75

A regra é:

const se aplica à coisa que resta dela. Se não houver nada à esquerda, isso se aplica à coisa certa.

Eu prefiro usar const à direita da coisa para ser const apenas porque é a maneira "original" de const ser definida.

Mas acho que esse é um ponto de vista muito subjetivo.

horstforst
fonte
18
Eu prefiro colocá-lo à esquerda, mas acho que colocá-lo à direita faz mais sentido. Você geralmente lê tipos em C ++ da direita para a esquerda, por exemplo, Object const *é um ponteiro para um objeto const. Se você colocar o constlado esquerdo, ele seria lido como um ponteiro para um Objeto que é const, que realmente não flui muito bem.
Collin Dauphinee
1
Tenho a impressão de que, à esquerda, é consistente com o estilo humano com outros tipos de declarações C (em termos de computador, não é correto, pois constnão é uma classe de armazenamento, mas as pessoas não são analisadoras).
geekosaur 31/03
1
@Heath Eu acredito que isso é mais uma diretriz do que uma regra, e eu ouvi isso muitas vezes como uma maneira de lembrar como o compilador irá interpretá-lo ... Eu entendo como ele funciona, então eu só estava curioso sobre o processo de pensamento por trás do decisão de apoiá-lo nos dois sentidos.
AJG85 31/03
3
@HeathHunnicutt a regra existe, mas é apenas um pouco mais complicada: c-faq.com/decl/spiral.anderson.html
imallett
2
@HeathHunnicutt: a regra em espiral é a versão expandida do comentário do primeiro comentarista "Você geralmente lê tipos em [C /] C ++ da direita para a esquerda". Eu presumi que você estava contradizendo isso. No entanto, acho que você pode estar se referindo à resposta em si.
imallett
56

Eu prefiro a segunda sintaxe. Isso me ajuda a acompanhar o que é constante lendo a declaração de tipo da direita para a esquerda:

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
Matt Davis
fonte
3
Exatamente. Um "ponteiro constante para um objeto constante" Object const* constnão é const const Object*. "const" não pode estar à esquerda, exceto no caso especial em que tantas pessoas o adoram. (Veja Heath acima.)
cdunn2001
38

A ordem das palavras-chave em uma declaração não é tão fixa assim. Existem muitas alternativas para "a única ordem verdadeira". Como isso

int long const long unsigned volatile i = 0;

ou deveria ser

volatile unsigned long long int const i = 0;

??

Bo Persson
fonte
25
+1 para uma definição totalmente confusa de uma variável simples. :)
Xeo 31/03
@rubenvb - Sim, infelizmente são. A gramática apenas diz que a decl-specifier-seqé uma sequência de decl-specifiers. Não há uma ordem dada pela gramática, e o número de ocorrências de cada palavra-chave é limitada apenas por algumas regras semânticas (você pode ter um const, mas dois long:-)
Bo Persson
3
@rubenvb - Sim, unsignedé um tipo, o mesmo que unsigned inte int unsigned. unsigned longé um outro tipo, a mesma como unsigned long inte int long unsigned. Veja o padrão?
Bo Persson
2
@ Bo: Eu vejo a bagunça, tenho que ter três para ver um padrão ;). OK, obrigado
rubenvb
1
Você costumava ser capaz de adicionar staticà confusão de palavras, mas apenas recentemente os compiladores reclamaram que staticprecisava vir em primeiro lugar.
precisa saber é o seguinte
8

A primeira regra é usar o formato que seus padrões de codificação local exigirem. Depois disso: colocar a constfrente não gera confusão quando os typedefs estão envolvidos, por exemplo:

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

Se o seu padrão de codificação permitir typedef de ponteiros, ele realmente deve insistir em colocar a const após o tipo. Em todos os casos, exceto quando aplicado ao tipo, const deve seguir o que se aplica; portanto, a coerência também argumenta a favor do const depois. Mas as diretrizes locais de codificação superam tudo isso; a diferença normalmente não é importante o suficiente para voltar e alterar todo o código existente.

James Kanze
fonte
Acho que isso pode destacar a razão pela qual não temos tipos de indicadores de ponteiros em nossos padrões vagamente definidos nesta loja.
AJG85 31/03
1
Minha política (quando eu decidir sozinho) é colocar o const depois (por uma questão de coerência) e não usar typedefs para ponteiros (ou typedefs em geral) :-). E BTW, string :: iterator vs. string :: const_iterator provavelmente também deve ser levado em consideração na sua decisão. (Só para confundir as coisas :-). Não há resposta certa).
James Kanze
Ah, sim, eu poderia ter incluído o comportamento de const std::string::const_iteratorbem para uma boa medida;)
AJG85
2
@ JamesKanze - Espere um minuto, me ajude aqui ... Não vejo a confusão no exemplo postado. O que mais poderia const IntPtr p1significar além de "ponteiro inteiro constante" (ou seja, "ponteiro constante para inteiro")? Ninguém em sã consciência, mesmo sem saber como IntPtré definido, pensaria que isso p1é mutável. E, por falar nisso, por que alguém assumiria incorretamente que isso *p1é imutável? Além do mais, colocar o const em qualquer outro lugar (por exemplo IntPtr const p1) não altera a semântica.
Todd Lehman
3
@ToddLehman Você pode não ver a confusão, mas a maioria dos programadores de C ++ entende e sistematicamente erra (sem dúvida, ajudado por coisas como std::vector<T>::const_iterator, onde não é o iterador que é const, mas o que ele está apontando).
precisa saber é o seguinte
7

Existem razões históricas para a esquerda ou direita serem aceitáveis. Stroustrup adicionou const ao C ++ em 1983 , mas não chegou ao C até C89 / C90.

Em C ++, há um bom motivo para sempre usar const à direita. Você será consistente em todos os lugares porque as funções de membro const devem ser declaradas desta maneira:

int getInt() const;
Nick Westgate
fonte
1
... bem, a "boa razão" não é tão convincente, porque outros locais possíveis para a "const" não significariam a mesma coisa. const int& getInt(); int& const getInt();
Maestro
1
@ Maestro: Estou sugerindo que int const& getInt();é melhor do que o equivalente, const int& getInt();considerando int& const getInt();que você o compara com é redundante (as referências já são constantes), embora legais e geralmente dará um aviso. De qualquer forma, const em uma função de membro altera o thisponteiro na função de Foo* constpara Foo const* const.
Nick Westgate
constem uma função membro não significa a mesma coisa - ou é void set(int)&;algum tipo de referência a uma função?
Davis Herring