525 votes

Utilisation de 'const' pour les paramètres des fonctions

Jusqu'où allez-vous avec const ? Faites-vous simplement des fonctions const lorsque c'est nécessaire, ou bien allez-vous jusqu'au bout et l'utiliser partout ? Par exemple, imaginez un mutateur simple qui prend un seul paramètre booléen :

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

Est-ce que const Personnellement, je choisis de l'utiliser largement, y compris les paramètres, mais dans ce cas, je me demande si cela en vaut la peine ?

J'ai aussi été surpris d'apprendre que l'on peut omettre const des paramètres dans une déclaration de fonction mais peut l'inclure dans la définition de la fonction, par ex :

fichier .h

void func(int n, long l);

fichier .cpp

void func(const int n, const long l)

Y a-t-il une raison à cela ? Cela me semble un peu inhabituel.

0 votes

Je ne suis pas d'accord. Le fichier .h doit également contenir les définitions des constantes. Si ce n'est pas le cas, si des paramètres constants sont passés à la fonction, le compilateur générera une erreur, car le prototype dans le fichier .h n'a pas les définitions constantes.

15 votes

Je suis d'accord :-) (Avec la question, pas le dernier commentaire !) Si une valeur ne doit pas être changée dans le corps de la fonction, cela peut aider à arrêter les bugs stupides de == ou =, vous ne devriez jamais mettre const dans les deux, (si c'est passé par valeur, vous devez autrement) Ce n'est pas assez sérieux pour entrer dans des arguments à ce sujet cependant !

27 votes

@selwyn : Même si vous passez un const int à la fonction, cependant, il va être copié (puisque ce n'est pas une référence), et donc le const-ness n'a pas d'importance.

529voto

rlerallut Points 2806

const est inutile lorsque l'argument est passé par valeur puisque vous ne modifierez pas puisque vous ne modifierez pas l'objet de l'appelant.

Faux.

Il s'agit d'auto-documenter votre code et vos hypothèses.

Si de nombreuses personnes travaillent sur votre code et que vos fonctions ne sont pas triviales, vous devriez marquer const tout ce que vous pouvez. Lorsque vous écrivez du code de qualité industrielle, vous devez toujours supposer que vos collègues sont des psychopathes qui essaient de vous avoir par tous les moyens possibles (d'autant plus qu'il s'agit souvent de vous-même dans le futur).

De plus, comme quelqu'un l'a mentionné plus tôt, il pourrait aider le compilateur à optimiser un peu les choses (bien que ce ne soit pas gagné).

56 votes

Tout à fait d'accord. Il s'agit de communiquer avec les gens et de limiter ce qui pourrait être fait avec une variable à ce qui devrait être fait.

36 votes

J'ai voté contre celui-ci. Je pense que vous diluez ce que vous essayez d'indiquer avec const lorsque vous l'appliquez à de simples arguments de type pass by value.

37 votes

J'ai voté pour celui-ci. Déclarer le paramètre 'const' ajoute des informations sémantiques au paramètre. Elles mettent en évidence ce que l'auteur original du code avait l'intention de faire et cela facilitera la maintenance du code au fil du temps.

242voto

Greg Rogers Points 18119

La raison en est que const pour le paramètre ne s'applique que localement dans la fonction, puisqu'elle travaille sur une copie des données. Cela signifie que la signature de la fonction est vraiment la même de toute façon. C'est probablement un mauvais style de faire cela souvent.

Personnellement, j'ai tendance à ne pas utiliser const sauf pour les paramètres de référence et de pointeur. Pour les objets copiés, cela n'a pas vraiment d'importance, bien que cela puisse être plus sûr car cela signale une intention au sein de la fonction. C'est vraiment une question d'appréciation. J'ai tendance à utiliser const_iterator lorsque je boucle sur quelque chose et je n'ai pas l'intention de le modifier. const l'exactitude des types de référence est rigoureusement maintenue.

77 votes

Je ne suis pas d'accord avec la partie "mauvais style". Abandon de const à partir de prototypes de fonctions présente l'avantage de ne pas devoir modifier le fichier d'en-tête si vous décidez d'abandonner la fonction const de la partie mise en œuvre plus tard.

11 votes

"J'ai personnellement tendance à ne pas utiliser const sauf pour les paramètres de référence et de pointeur." Peut-être devriez-vous clarifier cela en disant "J'ai tendance à ne pas utiliser de qualificatifs superflus dans les déclarations de fonction, mais à utiliser const où cela fait une différence utile."

8 votes

Je ne suis pas d'accord avec cette réponse. Je penche de l'autre côté et marque les paramètres const chaque fois que possible ; c'est plus expressif. Lorsque je lis le code de quelqu'un d'autre, j'utilise de petits indicateurs comme celui-ci pour juger du soin qu'il a apporté à l'écriture de son code, à côté de choses comme les chiffres magiques, les commentaires, l'utilisation correcte des pointeurs, etc.

182voto

Constantin Points 12185

Parfois (trop souvent !), je dois démêler le code C++ de quelqu'un d'autre. Et nous savons tous que de quelqu'un d'autre Le code C++ est un désordre complet presque par définition :) Donc la première chose que je fais pour déchiffrer le flux de données local est de mettre const dans chaque définition de variable jusqu'à ce que le compilateur commence à aboyer. Cela signifie que les arguments de valeur qualifiant les constantes aussi, parce qu'ils sont juste des variables locales fantaisistes initialisées par l'appelant.

Ah, j'aimerais que les variables soient const par défaut et mutable était nécessaire pour les variables non constantes :)

9 votes

"J'aimerais que les variables soient constantes par défaut" - un oxymore ?? 8-) Sérieusement, comment "consting" tout vous aide à démêler le code ? Si l'auteur original a changé un argument supposé constant, comment savez-vous que la variable était censée être une constante ? De plus, la grande majorité des variables (non-arguments) sont censées être... des variables. Donc, le compilateur devrait se casser très rapidement après avoir commencé le processus, non ?

17 votes

@ysap, 1. Marquer les constantes autant que possible me permet de voir quelles sont les parties qui bougent et celles qui ne bougent pas. D'après mon expérience, de nombreux locaux sont de facto const, et non l'inverse. 2. "Variable const"/"Variable immuable" peut sembler être un oxymore, mais c'est une pratique standard dans les langages fonctionnels, ainsi que dans certains langages non fonctionnels ; voir Rust par exemple : doc.rust-lang.org/book/variable-bindings.html

2 votes

Également standard maintenant dans certaines circonstances en c++ ; par exemple, le lambda [x](){return ++x;} est une erreur ; voir aquí

95voto

Ben Straub Points 3224

Les deux lignes suivantes sont fonctionnellement équivalentes :

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

Il est évident que vous ne pourrez pas modifier a dans le corps de foo si elle est définie de la deuxième façon, mais il n'y a pas de différence de l'extérieur.

const est vraiment utile avec les paramètres de référence ou de pointeur :

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

Cela signifie que foo peut prendre un paramètre important, par exemple une structure de données de plusieurs gigaoctets, sans le copier. De plus, cela indique à l'appelant que "Foo ne modifiera pas* le contenu de ce paramètre". Le passage d'une référence constante permet également au compilateur de prendre certaines décisions en matière de performances.

* : A moins que cela ne supprime la constance, mais c'est un autre sujet.

4 votes

Ce n'est pas le sujet de cette question ; bien sûr, pour les arguments référencés ou pointés, c'est une bonne idée d'utiliser const (si la valeur référencée ou pointée n'est pas modifiée). Notez que ce n'est pas le paramètre qui est constant dans votre exemple de pointeur ; c'est la chose vers laquelle le paramètre pointe qui l'est.

0 votes

> Le passage d'une référence constante permet également au compilateur de prendre certaines décisions en matière de performances. Erreur classique - le compilateur doit déterminer lui-même le caractère constant, le mot clé const n'y contribue pas grâce à l'aliasing des pointeurs et const_cast.

95voto

Adisak Points 3328

Les constantes superflues sont mauvaises du point de vue de l'API :

Placez des const superflus dans votre code pour les paramètres de type intrinsèque passés par valeur. encombre votre API tout en ne faisant aucune promesse significative à l'appelant ou à l'utilisateur de l'API (cela ne fait que gêner la mise en œuvre).

Trop de 'const' dans une API lorsqu'ils ne sont pas nécessaires, c'est comme " crier au loup ", les gens finiront par ignorer le terme " const " parce qu'il est partout et ne signifie rien la plupart du temps.

L'argument "reductio ad absurdum" pour que les constantes supplémentaires dans l'API soient bonnes pour ces deux premiers points serait que si plus de paramètres const sont bons, alors chaque argument qui peut avoir une const, DEVRAIT avoir une const. En fait, si c'était vraiment si bon, vous voudriez que const soit la valeur par défaut pour les paramètres et avoir un mot clé comme "mutable" uniquement lorsque vous voulez changer le paramètre.

Alors essayons de mettre des constantes partout où nous le pouvons :

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

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

Considérez la ligne de code ci-dessus. Non seulement la déclaration est plus encombrée, plus longue et plus difficile à lire, mais trois des quatre mots-clés "const" peuvent être ignorés sans risque par l'utilisateur de l'API. Cependant, l'utilisation supplémentaire de "const" a rendu la deuxième ligne potentiellement plus complexe. DANGEREUX !

Pourquoi ?

Une rapide erreur de lecture du premier paramètre char * const buffer pourrait vous faire penser qu'il ne modifiera pas la mémoire dans le tampon de données qui lui est passé - cependant, ce n'est pas vrai ! Les "const" superflus peuvent conduire à des hypothèses dangereuses et incorrectes concernant votre API. lorsqu'ils sont scannés ou mal lus rapidement.


Les constantes superflues sont également mauvaises du point de vue de la mise en œuvre du code :

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

Si FLEXIBLE_IMPLEMENTATION n'est pas vrai, alors l'API "promet" de ne pas implémenter la fonction de la première façon ci-dessous.

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

C'est une promesse très stupide à faire. Pourquoi faire une promesse qui n'apporte aucun avantage à votre interlocuteur et ne fait que limiter votre mise en œuvre ?

Les deux sont des implémentations parfaitement valides de la même fonction, donc vous n'avez fait que vous attacher une main derrière le dos inutilement.

En outre, il s'agit d'une promesse très superficielle qui est facilement (et légalement) contournée.

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

Regardez, je l'ai implémenté de cette façon de toute façon, même si j'ai promis de ne pas le faire - juste en utilisant une fonction d'enveloppe. C'est comme lorsque le méchant promet de ne pas tuer quelqu'un dans un film et ordonne à ses sbires de le faire à sa place.

Ces constantes superflues ne valent pas plus qu'une promesse d'un méchant de cinéma.


Mais la capacité à mentir est encore pire :

J'ai appris qu'il est possible de ne pas faire correspondre les const dans l'en-tête (déclaration) et le code (définition) en utilisant des const fallacieux. Les défenseurs de la const-happy prétendent que c'est une bonne chose puisque cela vous permet de mettre const uniquement dans la définition.

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

Cependant, l'inverse est vrai... vous pouvez mettre un const fallacieux uniquement dans la déclaration et l'ignorer dans la définition. Cela ne fait que rendre les const superflus dans une API plus une chose terrible et un mensonge horrible - voir cet exemple :

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

Tout ce que fait le const superflu est de rendre le code de l'implémenteur moins lisible en le forçant à utiliser une autre copie locale ou une fonction wrapper lorsqu'il veut changer la variable ou passer la variable par référence non const.

Regardez cet exemple. Lequel est le plus lisible ? Est-il évident que la seule raison de la variable supplémentaire dans la deuxième fonction est qu'un concepteur d'API a ajouté une constante superflue ?

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

Espérons que nous avons appris quelque chose ici. Les const superflus sont une pollution visuelle de l'API, une nuisance agaçante, une promesse superficielle et dénuée de sens, une entrave inutile, et conduisent parfois à des erreurs très dangereuses.

11 votes

Pourquoi les votes négatifs ? C'est beaucoup plus utile si vous laissez un bref commentaire sur un downvote.

9 votes

Le but de l'utilisation de l'argument const est de faire échouer la ligne marquée (plist = pnext). C'est une mesure de sécurité raisonnable de garder les arguments des fonctions immuables. Je suis d'accord avec votre point de vue selon lequel ils sont mauvais dans les déclarations de fonctions (puisqu'ils sont superflus), mais ils peuvent servir leurs objectifs dans le bloc d'implémentation.

2 votes

Il n'y a aucune raison d'encombrer votre API pour exposer les détails de la mise en œuvre interne. Il est assez simple de faire en sorte que votre implémentation prenne des paramètres T parm_foo et l'affecter immédiatement à un const &T foo=parm_foo et ensuite utiliser foo dans la fonction - vous pouvez imposer la constance de vos paramètres sans encombrement inutile de l'API pour vos utilisateurs finaux.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X