Uso de 'const' para parâmetros de função


397

Até onde você vai const? Você apenas faz funções constquando necessário, ou usa todo o porco e as usa em todos os lugares? Por exemplo, imagine um mutador simples que use um único parâmetro booleano:

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

Isso é constrealmente útil? Pessoalmente, opto por usá-lo extensivamente, incluindo parâmetros, mas, neste caso, gostaria de saber se vale a pena?

Também fiquei surpreso ao saber que você pode omitir constdos parâmetros em uma declaração de função, mas pode incluí-lo na definição da função, por exemplo:

arquivo .h

void func(int n, long l);

arquivo .cpp

void func(const int n, const long l)

Existe uma razão para isso? Parece um pouco incomum para mim.


Discordo. O arquivo .h também deve ter as definições const. Caso contrário, se os parâmetros const forem passados ​​para a função, o compilador gerará um erro, pois o protótipo no arquivo .h não possui as definições const.
Selwyn 25/09/08

10
Concordo. :-) (Com a pergunta, não o último comentário!) Se um valor não deve ser alterado no corpo da função, isso pode ajudar a interromper == ou = bugs tolos, você nunca deve colocar const em ambos, (se isso é passado por valor, você deve) Caso contrário, não é sério o suficiente para discutir sobre isso!
Chris Huang-Leaver

19
@selwyn: Mesmo se você passar uma const int para a função, ela será copiada (já que não é uma referência) e, portanto, a constância não importa.
Jalf 04/04

11
O mesmo debate está acontecendo nesta pergunta: stackoverflow.com/questions/1554750/…
Parcial

5
Sei que este post tem alguns anos, mas como um novo programador, fiquei pensando nessa pergunta e me deparei com essa conversa. Na minha opinião, se uma função não deve alterar um valor, seja uma referência ou uma cópia do valor / objeto, deve ser const. É mais seguro, é auto-documentado e é mais amigável para depuração. Mesmo para a função mais simples, que tem uma instrução, eu ainda uso const.

Respostas:


187

O motivo é que const para o parâmetro se aplica apenas localmente na função, pois está trabalhando em uma cópia dos dados. Isso significa que a assinatura da função é realmente a mesma de qualquer maneira. Provavelmente é um estilo ruim fazer muito isso.

Pessoalmente, tendem a não usar const, exceto para parâmetros de referência e ponteiro. Para objetos copiados, isso realmente não importa, embora possa ser mais seguro, pois sinaliza a intenção dentro da função. É realmente uma decisão judicial. No entanto, costumo usar const_iterator ao fazer um loop em algo e não pretendo modificá-lo, então acho que cada um é seu, desde que a correção const para tipos de referência seja rigorosamente mantida.


57
Não posso concordar com a parte do 'estilo ruim'. A constretirada dos protótipos de função tem a vantagem de que você não precisa alterar o arquivo de cabeçalho se decidir abandonar a constparte da implementação posteriormente.
Michał Górny

4
"Pessoalmente, não uso const, exceto os parâmetros de referência e ponteiro." Talvez você deva esclarecer que "Eu costumo não usar qualificadores supérfluos nas declarações de função, mas usar constonde isso faz uma diferença útil".
Deduplicator

3
Eu discordo desta resposta. Eu me inclino para o outro lado e marca parâmetros constsempre que possível; é mais expressivo. Quando li código de outra pessoa, eu uso pequenos indicadores como este para julgar quanto cuidado eles colocaram em escrever seu código ao lado de coisas como números de magia, comentando, e uso ponteiro adequado, etc.
Ultimater

4
int getDouble(int a){ ++a; return 2*a; }Tente isso. Obviamente, ++aele não tem nada a ver, mas pode ser encontrado em uma função longa, escrita por mais de um programador por um longo período de tempo. Eu sugiro fortemente escrever int getDouble( const int a ){ //... }que irá gerar um erro de compilação ao encontrar ++a;.
27617 dom_beau

3
É tudo uma questão de quem precisa de quais informações. Você fornece o parâmetro por valor para que o responsável pela chamada não precise saber nada sobre o que você faz (internamente) com ele. Então escreva class Foo { int multiply(int a, int b) const; }no seu cabeçalho. Na sua implementação, você se importa em prometer não alterar ae, bportanto, int Foo::multiply(const int a, const int b) const { }faz sentido aqui. (Sidenote: o chamador e os cuidados de implementação sobre o fato de que a função não altera seu Fooobjeto, assim a const no final da sua declaração)
CharonX

415

"const não faz sentido quando o argumento é passado por valor, pois você não modificará o objeto do chamador."

Errado.

Trata-se de documentar seu código e suas suposições.

Se seu código tem muitas pessoas trabalhando nele e suas funções não são triviais, você deve marcar "const" todo e qualquer um que puder. Ao escrever um código de força industrial, você sempre deve presumir que seus colegas de trabalho são psicopatas, tentando fazer com que você o faça da maneira que puderem (especialmente porque geralmente é você mesmo no futuro).

Além disso, como alguém mencionado anteriormente, pode ajudar o compilador a otimizar um pouco as coisas (embora seja um tiro no escuro).


41
Concordo plenamente. Trata-se de se comunicar com as pessoas e restringir o que poderia ser feito com uma variável ao que deveria ser feito.
Len Holgate

19
Eu votei contra. Acho que você dilui o que está tentando indicar com const quando o aplica a argumentos simples de passagem por valor.
tonylo 23/09/08

26
Eu votei neste. Declarar o parâmetro 'const' adiciona informações semânticas ao parâmetro. Eles destacam o que o autor original do código pretendia e isso ajudará na manutenção do código com o passar do tempo.
Richard Corden

13
@tonylo: você não entende. Trata-se de marcar uma variável local como const dentro de um bloco de código (que é uma função). Eu faria o mesmo para qualquer variável local. É ortogonal ter uma API que seja constante, o que também é importante.
rlerallut

56
E pode detectar bugs dentro da função - se você souber que um parâmetro não deve ser alterado, declará-lo const significa que o compilador informará se você o modificar acidentalmente.
Adrian

156

Às vezes (com muita frequência!) Eu tenho que desembaraçar o código C ++ de outra pessoa. E todos sabemos que o código C ++ de outra pessoa é uma bagunça completa quase por definição :) Portanto, a primeira coisa que faço para decifrar o fluxo de dados local é colocar const em todas as definições de variáveis ​​até o compilador começar a latir. Isso significa argumentos de valor de qualificação constante também, porque são apenas variáveis ​​locais sofisticadas inicializadas pelo chamador.

Ah, eu gostaria que as variáveis ​​fossem const por padrão e mutável fosse necessário para variáveis ​​não const :)


4
"Eu gostaria que as variáveis ​​fossem const por padrão" - um oxímoro? 8-) Sério, como "consting" tudo ajuda a desembaraçar o código? Se o escritor original mudou um argumento supostamente constante, como você sabe que o var deveria ser uma constante? Além disso, a grande maioria das variáveis ​​(sem argumento) deve ser ... variáveis. Então o compilador deve quebrar logo após o início do processo, não?
ysap

8
@ysap, 1. Marcando const o máximo possível, permite-me ver quais partes estão se movendo e quais não estão. Na minha experiência, muitos locais são de fato const, e não o contrário. 2. "Const variável" / "Variável imutável" pode parecer oxímoro, mas é uma prática padrão em linguagens funcionais, bem como em algumas não funcionais; consulte Rust, por exemplo: doc.rust-lang.org/book/variable-bindings.html
Constantin

11
Também padrão agora em algumas circunstâncias em c ++; por exemplo, o lambda [x](){return ++x;}é um erro; veja aqui
anatolyg

10
As variáveis são " const" por padrão em Rust :)
Phoenix

As variáveis ​​não precisam necessariamente ser atribuíveis para permitir que elas variem; o valor com o qual eles são inicializados também pode variar em tempo de execução.
Sphynx

80

As duas linhas a seguir são funcionalmente equivalentes:

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

Obviamente, você não poderá modificar ano corpo foose ele for definido da segunda maneira, mas não há diferença do lado de fora.

Onde constrealmente é útil é com parâmetros de referência ou ponteiro:

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

O que isso diz é que foo pode receber um parâmetro grande, talvez uma estrutura de dados de tamanho em gigabytes, sem copiá-lo. Além disso, ele diz ao chamador: "Foo não * altera o conteúdo desse parâmetro". Passar uma referência const também permite que o compilador tome certas decisões de desempenho.

*: A menos que elimine a constância, mas isso é outro post.


3
Não é disso que se trata esta questão; é claro que para argumentos referenciados ou apontados, é uma boa idéia usar const (se o valor referenciado ou apontado não for modificado). Observe que não é o parâmetro const no seu exemplo de ponteiro; é o que o parâmetro aponta para isso.
tml 5/05/16

> A passagem de uma referência const também permite que o compilador tome certas decisões de desempenho. falácia clássica - o compilador tem de determinar para si a const-ness, a palavra-chave const não ajudar com isso graças à aliasing ponteiro e const_cast
jheriko

73

Const extra supérflua é ruim do ponto de vista da API:

Colocar constantes supérfluas extras em seu código para parâmetros intrínsecos passados ​​por valor atrapalha sua API, sem fazer nenhuma promessa significativa ao chamador ou usuário da API (apenas prejudica a implementação).

Excesso de 'const' em uma API quando não é necessário é como " lobo chorando "; eventualmente, as pessoas começarão a ignorar 'const' porque está em todo lugar e não significa nada na maioria das vezes.

O argumento "reductio ad absurdum" para considerações extras na API é bom, pois esses dois primeiros pontos seriam: se mais parâmetros const forem bons, todos os argumentos que possam ter uma const, devem ter uma const. De fato, se fosse realmente tão bom, você desejaria que const seja o padrão para parâmetros e tenha uma palavra-chave como "mutável" somente quando desejar alterar o parâmetro.

Então, vamos tentar colocar const sempre que possível:

void mungerum(char * buffer, const char * mask, int count);

void mungerum(char * const buffer, const char * const mask, const int count);

Considere a linha de código acima. A declaração não é apenas mais confusa, longa e difícil de ler, mas três das quatro palavras-chave 'const' podem ser ignoradas com segurança pelo usuário da API. No entanto, o uso extra de 'const' tornou a segunda linha potencialmente PERIGOSA!

Por quê?

Uma rápida leitura incorreta do primeiro parâmetro char * const bufferpode fazer você pensar que ele não modificará a memória no buffer de dados que é passado - no entanto, isso não é verdade! 'Const' supérfluo pode levar a suposições perigosas e incorretas sobre sua API quando digitalizadas ou mal interpretadas rapidamente.


Const supérfluo também é ruim do ponto de vista de implementação de código:

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

Se FLEXIBLE_IMPLEMENTATION não for verdadeiro, a API é "promissora" para não implementar a função da primeira maneira abaixo.

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

Essa é uma promessa muito boba de se fazer. Por que você deve fazer uma promessa que não oferece benefício algum ao seu interlocutor e limita apenas sua implementação?

Ambas são implementações perfeitamente válidas da mesma função; portanto, tudo o que você fez foi amarrar uma mão pelas costas desnecessariamente.

Além disso, é uma promessa muito superficial que é fácil (e contornada legalmente).

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

Olha, eu implementei dessa maneira de qualquer maneira, apesar de prometer não fazê-lo - apenas usando uma função de wrapper. É como quando o bandido promete não matar alguém em um filme e ordena que seu capanga o mate.

Essas constantes supérfluas não valem mais do que a promessa de um bandido de cinema.


Mas a capacidade de mentir fica ainda pior:

Fui informado de que você pode incompatível const no cabeçalho (declaração) e código (definição) usando const espúria. Os advogados const-happy afirmam que isso é uma coisa boa, pois permite que você coloque const apenas na definição.

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

No entanto, o inverso é verdadeiro ... você pode colocar uma const espúria apenas na declaração e ignorá-la na definição. Isso torna a const supérflua em uma API apenas uma coisa terrível e uma mentira horrível - veja este exemplo:

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
}

Tudo o que a const supérflua realmente faz é tornar o código do implementador menos legível, forçando-o a usar outra cópia local ou uma função de wrapper quando ele deseja alterar a variável ou passar a variável por referência não const.

Veja este exemplo. Qual é mais legível? É óbvio que a única razão para a variável extra na segunda função é porque algum designer de API lançou uma const supérflua?

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

Espero que tenhamos aprendido algo aqui. Const supérfluo é um desagradável desordem da API, uma chatice irritante, uma promessa superficial e sem sentido, um obstáculo desnecessário e, ocasionalmente, leva a erros muito perigosos.


9
Por que os votos negativos? É muito mais útil se você deixar um breve comentário em um voto negativo.
Adisak

7
O objetivo de usar o argumento const é fazer com que a linha marcada falhe (plist = pnext). É uma medida de segurança razoável para manter o argumento da função imutável. Concordo com o seu ponto de vista de que eles são ruins nas declarações de funções (por serem supérfluos), mas podem servir a seus propósitos no bloco de implementação.
Touko ''

23
@ Adisak Não vejo nada de errado com a sua resposta, por si só, mas parece pelos seus comentários que você está perdendo um ponto importante. A definição / implementação da função não faz parte da API, que é apenas a declaração da função . Como você disse, declarar funções com parâmetros const é inútil e adiciona confusão. No entanto, os usuários da API talvez nunca precisem ver sua implementação. Enquanto isso, o implementador pode decidir qualificar alguns dos parâmetros na definição da função SOMENTE para maior clareza, o que é perfeitamente aceitável.
Jw013

17
@ jw013 está correto void foo(int)e void foo(const int)tem exatamente a mesma função, não sobrecargas. ideone.com/npN4W4 ideone.com/tZav9R A const aqui é apenas um detalhe de implementação do corpo da função e não afeta a resolução de sobrecarga. Deixe o const fora da declaração, para uma API mais segura e organizada, mas coloque const na definição , se você não quiser modificar o valor copiado.
Oktalist

3
@ Adisak Eu sei que isso é antigo, mas acredito que o uso correto de uma API pública seria o contrário. Dessa forma, os desenvolvedores que trabalham nos internos não cometem erros, como pi++quando não deveriam.
CoffeeandCode

39

const deveria ter sido o padrão em C ++. Como isso :

int i = 5 ; // i is a constant

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

8
Exatamente meus pensamentos.
Constantin

24
A compatibilidade com C é muito importante, pelo menos para as pessoas que projetam C ++, para considerar isso.

4
Interessante, eu nunca tinha pensado nisso.
Dan

6
Da mesma forma, unsigneddeveria ter sido o padrão em C ++. Assim: int i = 5; // i is unsignede signed int i = 5; // i is signed.
precisa saber é o seguinte

25

Quando codifiquei C ++ como meio de vida, constituí tudo o que pude. Usar const é uma ótima maneira de ajudar o compilador a ajudá-lo. Por exemplo, manter os valores de retorno do método pode salvá-lo de erros de digitação, como:

foo() = 42

quando você quis dizer:

foo() == 42

Se foo () estiver definido para retornar uma referência não const:

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

O compilador permitirá que você atribua um valor ao temporário anônimo retornado pela chamada de função. Fazendo const:

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

Elimina essa possibilidade.


6
Com qual compilador isso funcionou? GCC dá um erro ao tentar compilar foo() = 42: error: lvalue exigido como operando à esquerda de cessão
Gavrie

Isso está incorreto. foo () = 42 é o mesmo que 2 = 3, ou seja, um erro do compilador. E retornar uma const é completamente inútil. Não faz nada para um tipo embutido.
Josh

2
Eu encontrei esse uso de const e posso dizer, no final, produz muito mais problemas do que benefícios. Dica: const int foo()é de um tipo diferente int foo(), o que cria grandes problemas se você estiver usando indicadores de função, sistemas de sinal / slot ou boost :: bind.
Mephane 23/09

2
Corrigi o código para incluir o valor de retorno de referência.
Avdi

Não é const int& foo()efetivamente o mesmo que int foo(), devido à otimização do valor de retorno?
Zantier

15

Há uma boa discussão sobre esse tópico nos antigos artigos "Guru da semana" sobre comp.lang.c ++. Moderados aqui .

O artigo correspondente do GOTW está disponível no site da Herb Sutter aqui .


11
Herb Sutter é um cara muito inteligente :-) Definitivamente vale a pena ler e concordo com TODOS os seus pontos.
Adisak

2
Bom artigo, mas eu discordo dele sobre os argumentos. Também os faço const porque são como variáveis ​​e nunca quero que ninguém faça alterações nos meus argumentos.
QBziZ

9

Eu digo const seus parâmetros de valor.

Considere esta função de buggy:

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

Se o parâmetro number fosse const, o compilador pararia e nos alertaria sobre o bug.


2
outra maneira é usar if (0 == number) ... else ...;
Johannes Schaub - litb 14/11

5
@ ChrisHuang-Leaver Horrível não é, se fala como Yoda você faz: stackoverflow.com/a/2430307/210916
MPelletier

GCC / Clang -Wall fornece -Wententeses, o que exige que você faça "if ((number = 0))" se é realmente isso que você pretendia fazer. O que funciona bem como um substituto para ser Yoda.
Jetski S-type

8

Eu uso const on parâmetros de função que são referências (ou ponteiros) que são apenas [no] dados e não serão modificados pela função. Ou seja, quando o objetivo de usar uma referência é evitar a cópia de dados e não permitir a alteração do parâmetro passado.

Colocar const no parâmetro booleano b no seu exemplo coloca apenas uma restrição na implementação e não contribui para a interface da classe (embora geralmente não seja recomendável alterar parâmetros).

A assinatura da função para

void foo(int a);

e

void foo(const int a);

é o mesmo, o que explica seus .c e .h

Asaf


6

Se você usa os operadores ->*ou .*, é uma obrigação.

Impede que você escreva algo como

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

o que eu quase fiz agora e que provavelmente não faz o que você pretende.

O que eu pretendia dizer era

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

e se eu tivesse colocado um constentre Bar *e p, o compilador teria me dito isso.


4
Eu imediatamente verificaria uma referência sobre a precedência do operador quando estou prestes a misturar muitos operadores (se ainda não conheço 100%), então o IMO não é um problema.
Mk12 26/08/12

5

Ah, uma pergunta difícil. Por um lado, uma declaração é um contrato e realmente não faz sentido passar um argumento const por valor. Por outro lado, se você observar a implementação da função, poderá oferecer ao compilador mais chances de otimizar se declarar um argumento constante.


5

const não faz sentido quando o argumento é passado por valor, pois você não modificará o objeto do chamador.

const deve ser preferido ao passar por referência, a menos que o objetivo da função seja modificar o valor passado.

Finalmente, uma função que não modifica o objeto atual (isto) pode e provavelmente deve ser declarada const. Um exemplo está abaixo:

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

Esta é uma promessa de não modificar o objeto ao qual esta chamada é aplicada. Em outras palavras, você pode ligar para:

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

Se a função não fosse const, isso resultaria em um aviso do compilador.


5

A marcação de parâmetros de valor 'const' é definitivamente uma coisa subjetiva.

No entanto, na verdade, prefiro marcar os parâmetros de valor const, como no seu exemplo.

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

O valor para mim indica claramente que os valores dos parâmetros da função nunca são alterados pela função. Eles terão o mesmo valor no início e no final. Para mim, é parte de manter um estilo de programação muito funcional.

Para uma função curta, é indiscutivelmente um desperdício de tempo / espaço ter o 'const' lá, já que geralmente é bastante óbvio que os argumentos não são modificados pela função.

No entanto, para uma função maior, é uma forma de documentação de implementação e é imposta pelo compilador.

Posso ter certeza de que, se fizer algum cálculo com 'n' e 'l', posso refatorar / mover esse cálculo sem medo de obter um resultado diferente porque perdi um lugar onde um ou ambos foram alterados.

Como é um detalhe de implementação, você não precisa declarar os parâmetros de valor const no cabeçalho, assim como não precisa declarar os parâmetros de função com os mesmos nomes que a implementação usa.


4

Pode ser que esse argumento não seja válido. mas se incrementarmos o valor de uma variável const dentro de um compilador de funções, ocorrerá um erro: " erro: incremento do parâmetro somente leitura ". então isso significa que podemos usar a palavra-chave const como uma maneira de impedir a modificação acidental de nossas variáveis ​​dentro de funções (que não devemos / somente leitura). por isso, se o fizermos acidentalmente no compilador, informaremos isso. isso é especialmente importante se você não é o único que está trabalhando neste projeto.


3

Eu costumo usar const sempre que possível. (Ou outra palavra-chave apropriada para o idioma de destino.) Faço isso puramente porque permite que o compilador faça otimizações extras que não seriam capazes de fazer de outra maneira. Como não tenho idéia de quais podem ser essas otimizações, sempre o faço, mesmo quando parece bobagem.

Pelo que sei, o compilador pode muito bem ver um parâmetro de valor const e dizer: "Ei, essa função não está modificando isso de qualquer maneira, para que eu possa passar por referência e salvar alguns ciclos de relógio". Eu acho que nunca faria isso, uma vez que altera a assinatura da função, mas isso é verdade. Talvez ele faça alguma manipulação de pilha diferente ou algo assim ... O ponto é, eu não sei, mas sei que tentar ser mais inteligente do que o compilador só me leva a ser envergonhado.

O C ++ possui uma bagagem extra, com a ideia de correção constante, tornando-se ainda mais importante.


Embora possa ajudar em alguns casos, suspeito que a possibilidade de promover otimizações seja drasticamente exagerada como um benefício const. Em vez disso, é uma questão de declarar a intenção na implementação e capturar pensamentos mais tarde (incrementar acidentalmente a variável local errada, porque não era const). Paralelamente, também acrescentaria que os compiladores são muito bem-vindos para alterar as assinaturas de funções, no sentido de que as funções podem ser incorporadas e, uma vez destacadas, toda a maneira como elas funcionam pode ser alterada; adicionar ou remover referências, fazer literais de 'variáveis', etc, estão todos dentro da regra como se
underscore_d

3

1. Melhor resposta com base na minha avaliação:

A resposta de @Adisak é a melhor resposta aqui, com base na minha avaliação. Observe que essa resposta é, em parte, a melhor porque também é a mais bem-suportada com exemplos de códigos reais , além de usar som e lógica bem pensada.

2. Minhas próprias palavras (concordando com a melhor resposta):

  1. Para passar por valor, não há benefício em adicionar const. Tudo o que faz é:
    1. limite o implementador a fazer uma cópia sempre que quiser alterar um parâmetro de entrada no código-fonte (essa alteração não teria efeitos colaterais, pois o que é passado já é uma cópia, pois é transmitido por valor). E, freqüentemente, alterar um parâmetro de entrada que é passado por valor é usado para implementar a função, portanto, adicionar em constqualquer lugar pode impedir isso.
    2. e adicionar constdesnecessariamente desorganiza o código comconst s em todos os lugares, afastando a atenção dos constque são realmente necessários para ter um código seguro.
  2. Ao lidar com ponteiros ou referências , no entanto, consté extremamente importante quando necessário e deve ser usado, pois evita efeitos colaterais indesejados com alterações persistentes fora da função e, portanto, cada ponteiro ou referência deve ser usado constquando o parm é apenas uma entrada, não é uma saída. Usar const apenas os parâmetros passados ​​por referência ou ponteiro tem o benefício adicional de tornar realmente óbvio quais parâmetros são ponteiros ou referências. É mais uma coisa a se destacar e dizer "Cuidado! Qualquer parâmetro ao constlado é uma referência ou ponteiro!".
  3. O que descrevi acima tem sido frequentemente o consenso alcançado em organizações profissionais de software nas quais trabalhei e foi considerado uma prática recomendada. Às vezes até, a regra tem sido rígida: "nunca use const em parâmetros passados ​​por valor, mas sempre em parâmetros passados ​​por referência ou ponteiro, se forem apenas entradas".

3) Palavras do Google (concordando comigo e a melhor resposta):

(No " Guia de estilo do Google C ++ ")

Para um parâmetro de função passado por valor, const não tem efeito no chamador, portanto, não é recomendado nas declarações de função. Vejo TotW # 109 .

O uso de const em variáveis ​​locais não é incentivado nem desencorajado.

Fonte: a seção "Uso de const" do Guia de Estilo do Google C ++: https://google.github.io/styleguide/cppguide.html#Use_of_const . Esta é realmente uma seção realmente valiosa, então leia a seção inteira.

Observe que "TotW # 109" significa "Dica da semana # 109: constdeclarações significativas de função" e também é uma leitura útil. É mais informativo e menos prescritivo sobre o que fazer e com base no contexto anterior à regra do Google C ++ Style Guide sobreconst citada acima, mas como resultado da clareza fornecida, a constregra citada acima foi adicionada ao Google C ++ Guia de estilo.

Observe também que, embora eu esteja citando o Guia de estilo do Google C ++ aqui em defesa da minha posição, isso NÃO significa que eu sempre sigo o guia ou sempre o recomendo. Algumas das coisas que eles recomendam são simplesmente estranhas, como a kDaysInAWeekconvenção de nomenclatura ao estilo de "Nomes Constantes" . No entanto, ainda é útil e relevante apontar quando uma das empresas técnicas e de software mais influentes e bem-sucedidas do mundo usa a mesma justificativa que eu e outras pessoas como a @Adisak para apoiar nossos pontos de vista sobre esse assunto.

4. O linter de Clang,, clang-tidytem algumas opções para isso:

A. É importante notar também que linter de Clang, clang-tidy, tem uma opção, readability-avoid-const-params-in-decls, descrito aqui , para apoiar a aplicação em uma base de código não usando constpara parâmetros de função passagem por valor :

Verifica se uma declaração de função possui parâmetros que são const de nível superior.

os valores const nas declarações não afetam a assinatura de uma função, portanto eles não devem ser colocados lá.

Exemplos:

void f(const string);   // Bad: const is top level.
void f(const string&);  // Good: const is not top level.

E aqui estão mais dois exemplos que estou me acrescentando por completude e clareza:

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. Ele também possui esta opção: readability-const-return-type- https://clang.llvm.org/extra/clang-tidy/checks/readability-const-return-type.html

5. Minha abordagem pragmática de como redigir um guia de estilo sobre o assunto:

Eu simplesmente copio e colo isso no meu guia de estilo:

[INÍCIO DE COPIAR / COLAR]

  1. Sempre use const nos parâmetros de função passados ​​por referência ou ponteiro quando seu conteúdo (para o que eles apontam) NÃO for alterado. Dessa forma, torna-se óbvio quando se espera que uma variável passada por referência ou ponteiro seja alterada, porque ela faltará const. Nesse caso de usoconst evita efeitos colaterais acidentais fora da função.
  2. Não é recomendável usar constparâmetros de função passados ​​por valor, porque constnão afeta o chamador: mesmo que a variável seja alterada na função, não haverá efeitos colaterais fora da função. Consulte os seguintes recursos para obter justificativa e insight adicionais:
    1. Seção "Guia de estilo do Google C ++" "Uso de const"
    2. "Dica da semana # 109: significativa constnas declarações de funções"
    3. Resposta do Stack Overflow da Adisak em "Uso de 'const' para parâmetros de função"
  3. " Nunca use o nível superior const[isto é, constem parâmetros passados ​​por valor ] em parâmetros de função em declarações que não são definições (e tenha cuidado para não copiar / colar um significado const). Ele não faz sentido e é ignorado pelo compilador, é ruído visual. , e pode enganar os leitores "( https://abseil.io/tips/109 , grifo do autor).
    1. Os únicos constqualificadores que afetam a compilação são aqueles colocados na definição da função, NÃO aqueles em uma declaração direta da função, como em uma declaração da função (método) em um arquivo de cabeçalho.
  4. Nunca use nível superior const[ie: constem variáveis passadas por valor ] em valores retornados por uma função.
  5. O uso constde ponteiros ou referências retornados por uma função depende do implementador , pois às vezes é útil.
  6. TODO: aplique algumas das clang-tidyopções acima com as seguintes opções:
    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

Aqui estão alguns exemplos de código para demonstrar as constregras descritas acima:

constExemplos de parâmetros:
(alguns são emprestados daqui )

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]

constExemplos de tipos de retorno:
(alguns são emprestados daqui )

// 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();

[FIM DE COPIAR / COLAR]


2

No caso mencionado, isso não afeta os chamadores da sua API, e é por isso que isso não é feito com frequência (e não é necessário no cabeçalho). Isso afeta apenas a implementação da sua função.

Não é uma coisa particularmente ruim de se fazer, mas os benefícios não são tão bons, pois não afetam sua API e adicionam digitação, por isso geralmente não é feito.


2

Eu não uso const para parametere com valor passado. O chamador não se importa se você modifica o parâmetro ou não, é um detalhe de implementação.

O que é realmente importante é marcar os métodos como const se eles não modificarem sua instância. Faça isso à medida que avança, porque, caso contrário, você poderá acabar com muitas const_cast <> ou poderá achar que marcar uma const de método requer alterar muito código, pois chama outros métodos que deveriam ter sido marcados como const.

Eu também tendem a marcar const Vars local, se eu não precisar modificá-los. Eu acredito que facilita a compreensão do código, facilitando a identificação das "partes móveis".



2

Eu uso const onde posso. Const para parâmetros significa que eles não devem alterar seu valor. Isso é especialmente valioso ao passar por referência. const for function declara que a função não deve alterar os membros das classes.


2

Para resumir:

  • "Normalmente, a passagem constante por valor é inútil e enganosa, na melhor das hipóteses." De GOTW006
  • Mas você pode adicioná-los no .cpp, como faria com as variáveis.
  • Observe que a biblioteca padrão não usa const. Por exemplo std::vector::at(size_type pos). O que é bom o suficiente para a biblioteca padrão é bom para mim.

2
"O que é bom o suficiente para a biblioteca padrão é bom para mim" nem sempre é verdade. Por exemplo, a biblioteca padrão usa nomes de variáveis ​​feios _Tmpo tempo todo - você não deseja isso (na verdade, não tem permissão para usá-los).
Anatolyg

11
@anatolyg este é um detalhe de implementação
Fernando Pelliccioni

2
OK, os nomes de variáveis ​​e os tipos const-qualificados nas listas de argumentos são detalhes de implementação. O que quero dizer é que a implementação da biblioteca padrão às vezes não é boa. Às vezes, você pode (e deve) fazer melhor. Quando foi escrito o código da biblioteca padrão - 10 anos atrás? 5 anos atrás (algumas partes mais novas)? Podemos escrever um código melhor hoje.
precisa saber é

1

Se o parâmetro é passado por valor (e não é uma referência), geralmente não há muita diferença se o parâmetro é declarado como const ou não (a menos que contenha um membro de referência - não é um problema para os tipos internos). Se o parâmetro é uma referência ou ponteiro, geralmente é melhor proteger a memória referenciada / apontada para ele, não o ponteiro propriamente dito (acho que você não pode tornar a referência em si constante, não que isso importe tanto quanto você não pode mudar o árbitro) . Parece uma boa ideia proteger tudo o que puder como const. Você pode omiti-lo sem medo de cometer um erro se os parâmetros forem apenas PODs (incluindo tipos internos) e não houver chance deles mudarem mais ao longo da estrada (por exemplo, no seu exemplo, o parâmetro bool).

Eu não sabia sobre a diferença de declaração de arquivo .h / .cpp, mas faz algum sentido. No nível do código da máquina, nada é "const"; portanto, se você declarar uma função (no .h) como não-const, o código será o mesmo que se você a declarar como const (otimizações à parte). No entanto, ajuda a recrutar o compilador para que você não altere o valor da variável na implementação da função (.ccp). Pode ser útil no caso em que você está herdando de uma interface que permite alterações, mas não precisa alterar o parâmetro para obter a funcionalidade necessária.


0

Eu não colocaria const em parâmetros assim - todo mundo já sabe que um booleano (ao contrário de um booleano &) é constante; portanto, adicioná-lo fará com que as pessoas pensem "espere, o que?" ou mesmo que você esteja passando o parâmetro por referência.


4
Às vezes, você deseja passar um objeto por referência (por motivos de desempenho), mas não alterá-lo, portanto, const é obrigatório. Manter todos esses parâmetros - até bools - const seria uma boa prática, facilitando a leitura do seu código.
gbjbaanb 22/09/08

0

o que devemos lembrar com const é que é muito mais fácil tornar as coisas constantes desde o início, do que tentar colocá-las mais tarde.

Use const quando quiser que algo permaneça inalterado - é uma dica adicional que descreve o que sua função faz e o que esperar. Eu já vi muitas APIs C que poderiam fazer com algumas delas, especialmente aquelas que aceitam c-strings!

Eu estaria mais inclinado a omitir a palavra-chave const no arquivo cpp do que no cabeçalho, mas como eu costumo recortar e colá-las, elas serão mantidas nos dois lugares. Não tenho idéia do por que o compilador permite isso, acho que é uma coisa do compilador. A melhor prática é definitivamente colocar sua palavra-chave const nos dois arquivos.


Eu não entendo nada disso. Por que você estaria inclinado a omiti-lo no arquivo cpp (definição de função)? É aí que realmente significa algo e pode detectar erros. Por que você acha que é uma boa prática colocar const nos dois lugares? No arquivo de cabeçalho (declaração da função), isso não significa nada e desorganiza a API. Talvez exista algum valor pequeno em ter o decl e o defn com a mesma aparência, mas parece-me que esse é um benefício realmente menor se comparado ao problema de desordenar a API.
Don escotilha

@DonHatch 8 anos depois, uau. De qualquer forma, como o OP disse "Fiquei surpreso ao saber que você pode omitir const dos parâmetros em uma declaração de função, mas pode incluí-lo na definição da função".
Gbjbaanb

0

Não há realmente nenhuma razão para criar um parâmetro de valor "const", pois a função só pode modificar uma cópia da variável.

O motivo para usar "const" é se você está passando algo maior (por exemplo, uma estrutura com muitos membros) por referência; nesse caso, garante que a função não possa modificá-lo; ou melhor, o compilador reclamará se você tentar modificá-lo da maneira convencional. Impede que seja acidentalmente modificado.


0

O parâmetro Const é útil apenas quando o parâmetro é passado por referência, ou seja, referência ou ponteiro. Quando o compilador vê um parâmetro const, certifique-se de que a variável usada no parâmetro não seja modificada no corpo da função. Por que alguém iria querer tornar um parâmetro por valor tão constante? :-)


Por muitas razões. Criar um parâmetro por valor constdeclara claramente: 'Não preciso modificar isso, por isso estou declarando isso. Se eu tentar modificá-lo mais tarde, me dê um erro em tempo de compilação para que eu possa corrigir meu erro ou desmarcar como const'. Portanto, é uma questão de higiene e segurança do código. Por tudo o que é necessário adicionar aos arquivos de implementação, deve ser algo que as pessoas fazem como um reflexo puro, IMO.
underscore_d

0

Como os parâmetros estão sendo passados ​​por valor, isso não faz diferença se você especificar const ou não da perspectiva da função de chamada. Basicamente, não faz sentido declarar os parâmetros de passagem por valor como const.


0

Todas as vantagens em seus exemplos não têm propósito. C ++ é passado por valor por padrão, portanto, a função obtém cópias dessas entradas e booleanos. Mesmo que a função os modifique, a cópia do chamador não é afetada.

Então, eu evitaria consts extras porque

  • Eles são redundantes
  • Eles bagunçam o texto
  • Eles me impedem de alterar o valor passado nos casos em que possa ser útil ou eficiente.

-1

Eu sei que a pergunta está "um pouco" desatualizada, mas como eu vim através dela, outra pessoa também poderá fazê-lo no futuro ... ... ainda assim, duvido que o pobre sujeito esteja listado aqui para ler meu comentário :)

Parece-me que ainda estamos confinados à maneira de pensar no estilo C. No paradigma OOP, brincamos com objetos, não com tipos. O objeto Const pode ser conceitualmente diferente de um objeto não-const, especificamente no sentido de const-lógico (em contraste com const-a bit a bit). Assim, mesmo que a correção constante dos parâmetros de função seja (talvez) um excesso de cuidado no caso de PODs, não é assim no caso de objetos. Se uma função trabalha com um objeto const, deve-se dizer isso. Considere o seguinte snippet de código

#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;

}

ps .: você pode argumentar que a referência (const) seria mais apropriada aqui e oferece o mesmo comportamento. Bem, certo. Apenas dando uma imagem diferente do que eu podia ver em outro lugar ...

Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.