web-dev-qa-db-fra.com

Est-il préférable de supprimer "const" devant les types "primitifs" utilisés comme paramètres de fonction dans l'en-tête?

Dans le processus de révision du code, un de mes collègues m'a mentionné que les "const" devant les "types primitifs" utilisés comme paramètre de fonction dans un en-tête n'avaient pas de sens, et il a recommandé de supprimer ces "const". Il a suggéré d'utiliser "const" uniquement dans le fichier source dans de tels cas. Les types primitifs signifient des types tels que "int", "char", "float", etc.

Voici un exemple.

exemple.h

int ProcessScore(const int score);

exemple.cc

int ProcessScore(const int score) {
  // Do some calculation using score
  return some_value;
}

Sa suggestion est la suivante:

exemple.h

int ProcessScore(int score);  // const is removed here.

exemple.cc

int ProcessScore(const int score) {
  // Do some calculation using score
  return some_value;
}

Mais je suis quelque peu confus. Habituellement, l'utilisateur ne regardera que l'en-tête, donc s'il y a incohérence entre l'en-tête et le fichier source, cela risque de créer de la confusion.

Quelqu'un pourrait-il donner des conseils à ce sujet?

47
chanwcom

Pour tous les types (pas seulement les primitives), les qualificatifs niveau supérieur const dans la déclaration de fonction sont ignorés. Ainsi, les quatre suivants déclarent tous la même fonction:

void foo(int const i, int const j);
void foo(int i, int const j);
void foo(int const i, int j);
void foo(int i, int j);

Cependant, le qualificatif const n'est pas ignoré dans le corps de la fonction . Là, cela peut avoir un impact sur l'exactitude de la const. Mais c'est un détail d'implémentation de la fonction. Le consensus général est donc le suivant:

  1. Laissez le const hors de la déclaration. C'est juste de l'encombrement et n'affecte pas la façon dont les clients appellent la fonction.

  2. Laissez le const dans la définition si vous souhaitez que le compilateur intercepte toute modification accidentelle du paramètre.

68
StoryTeller

Le paramètre de fonction déclaré const et sans const est le même lors de la résolution de surcharge. Ainsi, par exemple, les fonctions

void f(int);
void f(const int);

sont les mêmes et ne peuvent pas être définis ensemble. Par conséquent, il est préférable de ne pas utiliser du tout const dans la déclaration des paramètres pour éviter d'éventuelles duplications. (Je ne parle pas de référence const ou de pointeur const - puisque le modificateur const n'est pas de niveau supérieur.)

Voici une citation exacte de la norme.

Après avoir produit la liste des types de paramètres, tous les qualificateurs cv de niveau supérieur modifiant un type de paramètre sont supprimés lors de la formation du type de fonction. La liste résultante des types de paramètres transformés et la présence ou l’absence des points de suspension ou d’un ensemble de paramètres de fonction est la liste des types de paramètres de la fonction. [Remarque: Cette transformation n'affecte pas les types de paramètres. Par exemple, int(*)(const int p, decltype(p)*) et int(*)(int, const int*) sont de types identiques. - note de fin]

L'utilité de const dans la définition de la fonction est discutable - le raisonnement derrière est le même que en utilisant const pour déclarer la variable locale - il montre aux autres programmeurs lisant le code que cette valeur ne sera pas modifiée à l'intérieur du une fonction.

41
Artemy Vysotsky

Suivez les recommandations qui vous ont été données lors de la révision du code.

L'utilisation de const pour les arguments de valeur n'a pas de valeur sémantique - c'est seulement significatif (potentiellement) pour l'implémentation de votre fonction - et même dans ce cas, je dirais que ce n'est pas nécessaire.

edit: Juste pour être clair: le prototype de votre fonction est l'interface publique de votre fonction. Ce que const fait est de garantir que vous ne modifierez pas les références.

int a = 7;
do_something( a );

void do_something(       int& x );  // 'a' may be modified
void do_something( const int& x );  // I will not modify 'a'
void do_something(       int  x );  // no one cares what happens to x

L'utilisation de const est quelque chose de similaire à TMI - elle est sans importance n'importe où sauf à l'intérieur de la fonction, que 'x' soit modifié ou non.

edit2: J'aime aussi beaucoup les informations de réponse de StoryTeller

15
Dúthomhas

Comme de nombreuses autres personnes ont répondu, du point de vue de l'API, les éléments suivants sont tous équivalents et égaux pour la résolution de surcharge:

void foo( int );
void foo( const int );

Mais une meilleure question est de savoir si cela fournit ou non une signification sémantique à un consommateur de cette API, ou s'il fournit une application des bons comportements à partir d'un développeur de l'implémentation.

Sans directives de codage de développeur bien définies qui le définissent expressément, const les arguments scalaires n'ont pas de signification sémantique évidente .

D'un consommateur:const int Ne change pas votre saisie. Il peut toujours s'agir d'un littéral ou d'une autre variable (à la fois const ou non - const)

De la part d'un développeur:const int Impose une restriction sur une copie locale d'une variable (dans ce cas, un argument de fonction). Cela signifie simplement de modifier l'argument, vous prenez une autre copie de la variable et la modifiez à la place.

Lors de l'appel d'une fonction qui accepte un argument par valeur, une copie est faite de cet argument sur la pile de la fonction appelée. Cela donne à la fonction une copie locale de l'argument pour toute sa portée qui peut ensuite être modifiée, utilisée pour les calculs, etc. - sans affecter l'entrée d'origine transmise à l'appel. En effet, cela fournit un argument de variable locale de son entrée.

En marquant l'argument comme const, cela signifie simplement que cette copie ne peut pas être modifiée; mais cela n'interdit pas au développeur de le copier et d'apporter des modifications à cette copie. Étant donné qu'il s'agissait d'une copie depuis le début, elle n'applique pas grand-chose de l'intérieur de l'implémentation - et, finalement, ne fait pas beaucoup de différence du point de vue du consommateur.

Cela contraste avec passant par référence , où une référence à int& Est sémantiquement différente de const int&. Le premier est capable de muter son entrée; ce dernier est seulement capable d'observer l'entrée (à condition que l'implémentation ne const_cast pas const - loin - mais laisse ignorer cette possibilité); ainsi, const - le fait d'avoir des références a une signification sémantique implicite.

Il n'offre pas beaucoup d'avantages dans l'API publique; et (imo) introduit des restrictions inutiles dans la mise en œuvre. Comme exemple artificiel et artificiel - une fonction simple comme:

void do_n_times( int n )
{
   while( n-- > 0 ) {
       // do something n times
   } 
}

devrait maintenant être écrit en utilisant une copie inutile:

void do_n_times( const int n )
{
    auto n_copy = n;
    while( n_copy-- > 0 ) {
        // do something n times
    }
}

Peu importe que les scalaires const soient utilisés dans l'API publique, un élément clé est d'être cohérent avec la conception. Si l'API bascule de manière aléatoire entre l'utilisation d'arguments scalaires const et l'utilisation de scalaires non const, cela peut entraîner une confusion quant à la signification implicite pour le consommateur.

TL; DR:const les types scalaires dans une API publique ne transmettent pas de signification sémantique à moins qu'ils ne soient explicitement définis par vos propres directives pour votre domaine.

7
Human-Compiler