Dovrebbero essere usati qualificatori di tipo inutili sui tipi di ritorno, per chiarezza?

Il nostro strumento di analisi statica si lamenta di un “qualificatore di tipo inutile sul tipo restituito” quando abbiamo prototipi nei file di intestazione come:

const int foo(); 

L’abbiamo definito in questo modo perché la funzione restituisce una costante che non cambierà mai, pensando che l’API fosse più chiara con const sul posto.

Mi sembra che questo sia simile all’inizializzazione esplicita delle variabili globali a zero per chiarezza, anche se lo standard C già afferma che tutti i globals saranno inizializzati a zero se non esplicitamente inizializzati. Alla fine della giornata, non importa. (Ma lo strumento di analisi statica non si lamenta di quello.)

La mia domanda è, c’è qualche ragione per cui questo potrebbe causare un problema? Dovremmo ignorare gli errori generati dallo strumento o dovremmo placare lo strumento al costo ansible di un’API meno chiara e coerente? (Restituisce altre const char* cui lo strumento non ha problemi.)

Di solito è meglio che il tuo codice descriva il più accuratamente ansible quello che sta succedendo. Stai ricevendo questo avviso perché const in const int foo(); è fondamentalmente privo di significato. L’API sembra solo più chiara se non sai cosa significa la parola chiave const . Non sovraccaricare il significato in questo modo; static è già abbastanza grave e non c’è motivo di aggiungere il potenziale per una maggiore confusione.

const char * indica qualcosa di diverso da const int , motivo per cui il tuo strumento non si lamenta di ciò. Il primo è un puntatore a una stringa costante, nel senso che qualsiasi codice che richiami la funzione che restituisce quel tipo non dovrebbe provare a modificare il contenuto della stringa (potrebbe essere in ROM, ad esempio). In quest’ultimo caso, il sistema non ha modo di imporre il fatto che non si apportano modifiche all’intestazione restituita, quindi il qualificatore non ha senso. Un parallelo più vicino ai tipi di ritorno sarebbe:

 const int foo(); char * const foo2(); 

che causeranno entrambi l’analisi statica per dare l’avviso – l’aggiunta di un qualificatore const a un valore restituito è un’operazione priva di significato. Ha senso solo quando hai un parametro di riferimento (o tipo di ritorno), come il tuo esempio const char * .

In effetti, ho appena realizzato un piccolo programma di test e GCC ha persino avvertito esplicitamente di questo problema:

 test.c:6: warning: type qualifiers ignored on function return type 

Quindi non è solo il tuo programma di analisi statico che si lamenta.

Puoi utilizzare una tecnica diversa per illustrare le tue intenzioni senza rendere gli strumenti infelici.

 #define CONST_RETURN CONST_RETURN int foo(); 

Non hai problemi con const char * perché questo è un puntatore a caratteri costanti, non un puntatore costante.

Ignorando il const per ora, foo() restituisce un valore. Tu puoi fare

 int x = foo(); 

e assegna il valore restituito da foo() alla variabile x , più o meno allo stesso modo che puoi fare

 int x = 42; 

per assegnare il valore 42 alla variabile x.
Ma non puoi cambiare il 42 … o il valore restituito da foo() . Dicendo che il valore restituito da foo() non può essere modificato, applicando la parola chiave const al tipo di foo() non si ottiene nulla.

I valori non possono essere const ( o restrict o volatile ). Solo gli oggetti possono avere qualificatori di tipo.


Contrasto con

 const char *foo(); 

In questo caso, foo() restituisce un puntatore a un object. L’object puntato dal valore restituito può essere qualificato const .

L’int è restituito dalla copia . Può essere una copia di un const, ma quando è assegnato a qualcos’altro, quel qualcosa in virtù del fatto che è assegnabile, non può essere per definizione un const.

La parola chiave const ha una semantica specifica all’interno della lingua, mentre qui si sta abusando di essa come essenzialmente un commento. Piuttosto che aggiungere chiarezza, suggerisce piuttosto un fraintendimento della semantica del linguaggio.

const int foo() è molto diverso da const char* foo() . const char* foo() restituisce un array (di solito una stringa) il cui contenuto non può cambiare. Pensa alla differenza tra:

  const char* a = "Hello World"; 

e

 const int b = 1; 

a è ancora una variabile e può essere assegnata ad altre stringhe che non possono cambiare mentre b non è una variabile. Così

 const char* foo(); const char* a = "Hello World\n"; a = foo(); 

è permesso ma

 const int bar(); const int b = 0; b = bar(); 

non è permesso, anche con la dichiarazione const di bar() .

Sì. Consiglierei di scrivere il codice “esplicitamente”, perché rende chiaro a chiunque (incluso te stesso) quando legge il codice cosa intendevi. Stai scrivendo codice per altri programmatori da leggere , non per soddisfare i capricci del compilatore e degli strumenti di analisi statici!

(Tuttavia, è necessario fare attenzione che qualsiasi “codice non necessario” non generi codice diverso da generare!)

Alcuni esempi di codifica esplicita che migliorano la leggibilità / la manutenibilità:

  • Metto parentesi attorno a porzioni di espressioni aritmetiche per specificare esplicitamente cosa voglio che accada. Questo rende chiaro a tutti i lettori cosa intendevo dire, e mi risparmia dovermi preoccupare (o fare errori con) delle regole di precedenza:

     int a = b + c * d / e + f;  // Difficile da leggere: serve conoscere la precedenza
     int a = b + ((c * d) / e) + f;  // Facile da leggere: cancellare calcoli espliciti
    

  • In C ++, se si esegue l’override di una funzione virtuale, nella class derivata è ansible dichiararlo senza menzionare “virtuale”. Chiunque legga il codice non può dire che è una funzione virtuale, che può essere disastrosamente fuorviante! Tuttavia puoi tranquillamente utilizzare la parola chiave virtuale:

      virtual int MyFunc () 

    e questo rende chiaro a chiunque legga l’intestazione della tua class che questo metodo sia virtuale. (Questo “errore di syntax C ++” è stato risolto in C # richiedendo l’uso della parola chiave “override” in questo caso – più prova se qualcuno ne avesse avuto bisogno che perdere il “virtuale non necessario” è una pessima idea)

Questi sono entrambi esempi chiari in cui l’aggiunta di codice “non necessario” renderà il codice più leggibile e meno sobject a bug.