Devem ser usados qualificadores de tipo inúteis em tipos de retorno, para maior clareza?
-
21-09-2019 - |
Pergunta
Nossa ferramenta de análise estática reclama de um "qualificador de tipo inútil no tipo de retorno" quando temos protótipos em arquivos de cabeçalho, como:
const int foo();
Definimos desta forma porque a função está retornando uma constante que nunca mudará, pensando que a API parecia mais clara com const
no lugar.
Eu sinto que isso é semelhante a inicializar explicitamente variáveis globais com zero para maior clareza, embora o padrão C já afirme que todos os globais serão inicializados com zero se não forem inicializados explicitamente.No final do dia, isso realmente não importa.(Mas a ferramenta de análise estática não reclama disso.)
Minha pergunta é: há algum motivo para que isso possa causar um problema?Devemos ignorar os erros gerados pela ferramenta ou devemos aplacá-la ao possível custo de uma API menos clara e consistente?(Ele retorna outro const char*
constantes com as quais a ferramenta não tem problemas.)
Solução
Geralmente é melhor que seu código descreva com a maior precisão possível o que está acontecendo.Você está recebendo este aviso porque o const
em const int foo();
é basicamente sem sentido.A API só parece mais clara se você não sabe o que const
palavra-chave significa.Não sobrecarregue significados assim; static
já é ruim o suficiente do jeito que está, e não há razão para acrescentar o potencial para mais confusão.
const char *
significa algo diferente de const int
faz, e é por isso que sua ferramenta não reclama disso.O primeiro é um ponteiro para uma string constante, o que significa que qualquer código que chame a função que retorna esse tipo não deve tentar modificar o conteúdo da string (pode estar na ROM, por exemplo).Neste último caso, o sistema não tem como garantir que você não faça alterações no valor retornado. int
, então o qualificador não tem sentido.Um paralelo mais próximo aos tipos de retorno seria:
const int foo();
char * const foo2();
o que fará com que sua análise estática emita o aviso - adicionar um qualificador const a um valor de retorno é uma operação sem sentido.Só faz sentido quando você tem um parâmetro de referência (ou tipo de retorno), como o seu const char *
exemplo.
Na verdade, acabei de fazer um pequeno programa de teste, e o GCC até avisa explicitamente sobre esse problema:
test.c:6: warning: type qualifiers ignored on function return type
Portanto, não é apenas o seu programa de análise estática que está reclamando.
Outras dicas
Você pode usar uma técnica diferente para ilustrar sua intenção sem deixar as ferramentas infelizes.
#define CONST_RETURN
CONST_RETURN int foo();
Você não tem problema com const char *
porque isso declara um ponteiro para caracteres constantes, não um ponteiro constante.
Ignorando o const
por agora, foo()
retorna um valor.Você pode fazer
int x = foo();
e atribua o valor retornado por foo()
para a variável x
, da mesma maneira que você pode fazer
int x = 42;
para atribuir o valor 42
para a variável x.
Mas você não pode mudar o 42
...ou o valor retornado por foo()
.Dizendo que o valor retornado de foo()
não pode ser alterado, aplicando o const
palavra-chave para o tipo de foo()
não realiza nada.
Valores não pode ser const
(ou restrict
, ou volatile
).Somente objetos podem ter qualificadores de tipo.
Contraste com
const char *foo();
Nesse caso, foo()
retorna um ponteiro para um objeto.O objeto apontado pelo valor retornado pode ser qualificado const
.
O int é retornado por cópia de.Pode ser uma cópia de uma const, mas quando é atribuída a outra coisa, essa coisa, em virtude do fato de ser atribuível, não pode, por definição, ser uma const.
A palavra-chave const possui semântica específica dentro da linguagem, mas aqui você a está utilizando indevidamente, essencialmente como um comentário.Em vez de acrescentar clareza, sugere um mal-entendido da semântica da linguagem.
const int foo()
é muito diferente de const char* foo()
. const char* foo()
retorna um array (geralmente uma string) cujo conteúdo não pode ser alterado.Pense na diferença entre:
const char* a = "Hello World";
e
const int b = 1;
a
ainda é uma variável e pode ser atribuída a outras strings que não podem ser alteradas, enquanto b
não é uma variável.Então
const char* foo();
const char* a = "Hello World\n";
a = foo();
é permitido, mas
const int bar();
const int b = 0;
b = bar();
não é permitido, mesmo com a const
declaração de bar()
.
Sim.Eu aconselharia escrever código "explicitamente", porque deixa claro para qualquer pessoa (inclusive você) ao ler o código o que você quis dizer.Você está escrevendo código para outros programadores para ler, para não agradar os caprichos do compilador e das ferramentas de análise estática!
(No entanto, você deve ter cuidado para que qualquer "código desnecessário" não cause a geração de código diferente!)
Alguns exemplos de codificação explícita melhorando a legibilidade/manutenção:
Coloco colchetes em torno de partes de expressões aritméticas para especificar explicitamente o que quero que aconteça.Isso deixa claro para qualquer leitor o que eu quis dizer e me poupa de ter que me preocupar com (ou cometer erros com) regras de precedência:
int a = b + c * d / e + f; // Hard to read- need to know precedence int a = b + ((c * d) / e) + f; // Easy to read- clear explicit calculations
Em C++, se você substituir uma função virtual, na classe derivada você poderá declará-la sem mencionar "virtual".Qualquer pessoa que leia o código não poderá dizer que se trata de uma função virtual, o que pode ser desastrosamente enganoso!No entanto, você pode usar com segurança a palavra-chave virtual:
virtual int MyFunc()
e isso deixa claro para qualquer pessoa que esteja lendo o cabeçalho da sua classe que esse método é virtual.(Este "bug de sintaxe C++" foi corrigido em C# exigindo o uso da palavra-chave "override" neste caso - mais prova, se alguém precisasse, de que perder o "virtual desnecessário" é uma péssima idéia)
Ambos são exemplos claros de onde adicionar código "desnecessário" tornará o código mais legível e menos sujeito a bugs.