web-dev-qa-db-fra.com

Utilisation de 'const' pour les paramètres de fonction

Jusqu'où allez-vous avec const? Faites-vous juste des fonctions const lorsque cela est nécessaire ou allez-vous tout faire pour les utiliser partout? Par exemple, imaginons un simple mutateur prenant un seul paramètre booléen:

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

Est-ce que const est réellement utile? Personnellement, j'opte pour une utilisation intensive, y compris des paramètres, mais dans ce cas, je me demande si cela en vaut la peine?

J'ai également été surpris d'apprendre que vous pouvez omettre const des paramètres dans une déclaration de fonction, mais que vous pouvez l'inclure dans la définition de la fonction, par exemple:

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.

360
Rob

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

J'ai personnellement 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, même si cela peut être plus sûr car cela signale l'intention de la fonction. C'est vraiment un jugement. Cependant, j’ai tendance à utiliser const_iterator lorsqu’on boucle quelque chose et je n’ai pas l’intention de le modifier. Je suppose donc que le sien est correct, tant que const correct pour les types de référence est rigoureusement maintenu.

169
Greg Rogers

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

Faux.

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

Si votre code a beaucoup de personnes qui travaillent dessus et que vos fonctions ne sont pas triviales, vous devriez marquer "const" tout ce que vous pouvez. Lorsque vous écrivez un code de qualité industrielle, vous devez toujours supposer que vos collègues sont des psychopathes qui essaient de vous trouver tous les moyens possibles (surtout que c'est souvent vous-même à l'avenir).

En outre, comme quelqu'un l'a mentionné plus tôt, cela peut-être aider le compilateur à optimiser les choses un peu (bien que ce soit un long plan).

392
rlerallut

Parfois (trop souvent!), Je dois démêler le code C++ de quelqu'un d'autre. Et nous savons tous que le code C++ de quelqu'un d'autre est un désordre total presque par définition :) Ainsi, la première chose que je fais pour déchiffrer le flux de données local est mise - const dans chaque définition de variable jusqu'à ce que le compilateur commence à aboyer. Cela signifie également des arguments de valeur qualifiant const, car ce ne sont que des variables locales fantaisistes initialisées par l'appelant.

Ah, j'aimerais que les variables soient const par défaut et modifiable était requis pour les variables non-const :)

143
Constantin

Les deux lignes suivantes sont fonctionnellement équivalentes:

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

Évidemment, vous ne pourrez pas modifier a dans le corps de foo s'il est défini de la deuxième façon, mais il n'y a pas de différence de l'extérieur.

const est vraiment utile, c’est avec des paramètres de référence ou de pointeur:

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

Cela dit, foo peut prendre un paramètre important, peut-être une structure de données d'une taille de 1 Go, sans le copier. En outre, il dit à l'appelant, "Foo ne * changera pas le contenu de ce paramètre." Passer une référence const permet également au compilateur de prendre certaines décisions de performance.

*: À moins que cela ne blesse la constance, mais c'est un autre post.

77
Ben Straub

Les agents extra-superflus sont mauvais du point de vue de l'API:

Mettre dans votre code des constantes superflues pour les paramètres de type intrinsèque passés par valeur encombre votre API sans faire de promesse significative à l'appelant ou à l'utilisateur de l'API (cela n'entrave que l'implémentation).

Trop de constants dans une API quand on n'en a pas besoin ressemblent à "loup qui pleure", à la longue les gens vont commencer à ignorer "const" car ils sont partout et ne signifient rien la plupart du temps.

L'argument "reductio ad absurdum" à des constants supplémentaires dans l'API convient à ces deux premiers points. Si plus de paramètres const sont bons, tous les arguments pouvant avoir un const, DEVRAIENT avoir un const. En fait, s'il était vraiment bon, vous voudriez que const soit le paramètre par défaut pour les paramètres et que le mot clé "mutable" ne soit utilisé que lorsque vous souhaitez modifier le paramètre.

Essayons donc de mettre const dans la mesure du possible:

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

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

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

Pourquoi?

Une erreur de lecture rapide du premier paramètre char * const buffer pourrait vous faire penser qu'il ne modifiera pas la mémoire dans la mémoire tampon de données transmise - toutefois, ce n'est pas vrai! 'const' superflu peut conduire à des suppositions dangereuses et incorrectes à propos de votre API lorsque numérisé ou mal interprété rapidement.


Les constantes superflues sont également mauvaises du point de vue de l’implémentation 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, l'API "promet" de ne pas implémenter la fonction de la première manière 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 devriez-vous faire une promesse qui ne procure aucun avantage à votre appelant et ne limite que votre mise en œuvre?

Ces deux solutions sont des implémentations parfaitement valables de la même fonction, de sorte que tout ce que vous avez fait est de nouer inutilement la main derrière le dos.

De plus, c’est 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);
}

Écoutez, je l’ai implémenté de cette façon même si j’avais promis de ne pas le faire - en utilisant simplement une fonction wrapper. C’est comme quand le méchant promet de ne pas tuer quelqu'un dans un film et ordonne à son homme de main de le tuer à la place.

Ces constants superflus ne valent rien de plus que la promesse d’un méchant du cinéma.


Mais la capacité de mentir est encore pire:

J'ai été éclairé sur le fait que vous ne pouvez pas associer const dans l'en-tête (déclaration) et le code (définition) à l'aide de const infâme. Les partisans de const-happy prétendent que c'est une bonne chose, car cela ne vous permet de mettre const que dans la définition.

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

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

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
}

Tout ce qui est super superflu, en réalité, rend le code de l'implémenteur moins lisible en le forçant à utiliser une autre copie locale ou une fonction d'encapsuleur lorsqu'il souhaite modifier la variable ou lui transmettre la référence 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 due au fait que certains concepteurs d'API ont jeté un const 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. Superfluous const est une horreur encombrante d’API, un fagot ennuyeux, une promesse superficielle et dénuée de sens, un obstacle inutile et conduit parfois à des erreurs très dangereuses.

68
Adisak

const aurait dû être la valeur par défaut en C++. Comme ça :

int i = 5 ; // i is a constant

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

Quand j'ai codé C++ pour gagner ma vie, j'ai tout fait pour le mieux. Utiliser const est un excellent moyen d’aider le compilateur à vous aider. Par exemple, la constitution des valeurs de retour de votre méthode peut vous éviter des fautes de frappe telles que:

foo() = 42

quand vous vouliez dire:

foo() == 42

Si foo () est défini pour renvoyer une référence non-const:

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

Le compilateur vous laissera volontiers attribuer une valeur au temporaire anonyme renvoyé par l'appel de la fonction. Ce qui en fait:

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

Élimine cette possibilité.

25
Avdi

Il existe une bonne discussion sur ce sujet dans les anciens articles du "gourou de la semaine" sur comp.lang.c ++. Modéré ici .

L'article correspondant de GOTW est disponible sur le site Web de Herb Sutter ici .

15
Void

Je dis const vos paramètres de valeur.

Considérez cette fonction buggy:

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

Si le paramètre number était const, le compilateur s'arrêterait et nous avertirait du bogue.

9
Ola Ost

J'utilise les paramètres const on function qui sont des références (ou des pointeurs) qui ne sont que des données et qui ne seront pas modifiés par la fonction. Signification, lorsque le but de l'utilisation d'une référence est d'éviter de copier les données et de ne pas permettre de changer le paramètre passé.

Mettre const sur le paramètre boolean b dans votre exemple ne fait que mettre une contrainte sur l'implémentation et ne contribue pas à l'interface de la classe (bien que ne pas modifier les paramètres soit généralement conseillé).

La signature de fonction pour

void foo(int a);

et

void foo(const int a);

est le même, ce qui explique votre .c et .h

Asaf

7
Asaf R

Si vous utilisez les opérateurs ->* ou .*, c'est un must.

Cela vous empêche d'écrire quelque chose comme

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

ce que j'ai presque fait en ce moment, et qui ne fait probablement pas ce que vous avez l'intention de faire.

Ce que je voulais dire était

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

et si j'avais placé un const entre Bar * et p, le compilateur me l'aurait dit.

7
Mehrdad

Ah, un dur. D'un côté, une déclaration est un contrat et il n'est pas logique de passer un argument const par valeur. D'autre part, si vous regardez l'implémentation de la fonction, vous donnez au compilateur plus de chances d'optimiser si vous déclarez une constante d'argument.

5
Nemanja Trifunovic

Marquer les paramètres de valeur 'const' est définitivement une chose subjective.

Cependant, je préfère en réalité marquer les paramètres de valeur const, comme dans votre exemple.

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

La valeur pour moi indique clairement que les valeurs des paramètres de la fonction ne sont jamais modifiées par la fonction. Ils auront la même valeur au début qu'à la fin. Pour moi, cela fait partie du maintien d’un style de programmation très fonctionnel.

Pour une fonction courte, c'est sans doute une perte de temps/espace d'avoir le 'const', car il est généralement assez évident que les arguments ne sont pas modifiés par la fonction.

Toutefois, pour une fonction plus importante, il s’agit d’une forme de documentation d’implémentation appliquée par le compilateur.

Je peux être sûr que si je fais un calcul avec 'n' et 'l', je peux refactoriser/déplacer ce calcul sans craindre d'obtenir un résultat différent car j'ai manqué un endroit où un ou les deux sont modifiés.

Comme il s'agit d'un détail d'implémentation, vous n'avez pas besoin de déclarer les paramètres de valeur const dans l'en-tête, tout comme vous n'avez pas besoin de déclarer les paramètres de fonction avec les mêmes noms que ceux utilisés par l'implémentation.

5
Lloyd

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

const devrait être préféré lors du passage par référence, sauf si le but de la fonction est de modifier la valeur passée.

Enfin, une fonction qui ne modifie pas l'objet courant (this) peut et devrait probablement être déclarée const. Un exemple est ci-dessous:

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

Ceci est une promesse de ne pas modifier l'objet auquel cet appel est appliqué. En d'autres termes, vous pouvez appeler:

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

Si la fonction n'était pas const, cela entraînerait un avertissement du compilateur.

4
Dan Hewett

Peut-être que ce ne sera pas un argument valable. mais si nous incrémentons la valeur d'une variable const dans un compilateur de fonctions, nous obtiendrons une erreur: "erreur: incrément de paramètre en lecture seule". cela signifie donc que nous pouvons utiliser la clé const Word pour éviter toute modification accidentelle de nos variables à l'intérieur de fonctions (ce que nous ne sommes pas censés modifier/en lecture seule). donc si nous l’avions accidentellement fait lors de la compilation, le compilateur nous le fera savoir. Ceci est particulièrement important si vous n'êtes pas le seul à travailler sur ce projet.

3
HarshaXsoad

Dans le cas que vous mentionnez, cela n'affecte pas les appelants de votre API, raison pour laquelle cela n'est pas fait couramment (et n'est pas nécessaire dans l'en-tête). Cela n'affecte que la mise en œuvre de votre fonction.

Ce n'est pas particulièrement une mauvaise chose à faire, mais les avantages sont médiocres car cela n'affecte pas votre API, et cela ajoute du dactylographie, donc ce n'est généralement pas le cas.

2
Luke Halliwell

J'utilise const où je peux. Const pour les paramètres signifie qu'ils ne doivent pas changer leur valeur. Ceci est particulièrement utile lors du passage par référence. const for function déclare que la fonction ne doit pas changer les membres de la classe.

2
Dror Helper

Je n'utilise pas const pour les paramètres transmis. L'appelant ne se soucie pas de savoir si vous modifiez le paramètre ou non, c'est un détail d'implémentation.

Ce qui est vraiment important, c'est de marquer les méthodes comme const si elles ne modifient pas leur instance. Faites-le au fur et à mesure, sinon vous pourriez vous retrouver avec beaucoup de const_cast <> ou vous pourriez trouver que marquer une méthode const nécessite de changer beaucoup de code car il appelle d'autres méthodes qui auraient dû être marquées const.

J'ai aussi tendance à marquer les variables locales si je n'ai pas besoin de les modifier. Je crois que cela rend le code plus facile à comprendre en facilitant l'identification des "pièces mobiles".

2
Aurélien Gâteau

Optimisations du compilateur: http://www.gotw.ca/gotw/081.htm

2
sdcvvc

J'ai tendance à utiliser const autant que possible. (Ou un autre mot clé approprié pour le langage cible.) Je le fais uniquement parce que cela permet au compilateur de faire des optimisations supplémentaires qu'il ne pourrait pas faire autrement. N'ayant aucune idée de ce que peuvent être ces optimisations, je le fais toujours, même lorsque cela semble idiot.

Pour autant que je sache, le compilateur pourrait très bien voir un paramètre de valeur const et dire: "Hé, cette fonction ne la modifie pas de toute façon, je peux donc passer par référence et économiser quelques cycles d'horloge." Je ne pense pas que cela ferait jamais une telle chose, car cela modifie la signature de la fonction, mais cela fait comprendre. Peut-être que cela fait des manipulations de pile différentes ou quelque chose comme ça ... Le fait est que je ne sais pas, mais je sais qu'essayer d'être plus intelligent que le compilateur ne fait que me faire honte.

C++ a un bagage supplémentaire, avec l'idée de const-correct, il devient donc encore plus important.

2
jdmichal

Résumer:

  • "Normalement, le passage par valeur de Const est inutile et trompeur au mieux." De GOTW006
  • Mais vous pouvez les ajouter dans le fichier .cpp comme vous le feriez avec des variables.
  • Notez que la bibliothèque standard n'utilise pas const. Par exemple. std::vector::at(size_type pos). Ce qui est bon pour la bibliothèque standard est bon pour moi.
1
YvesgereY

Si le paramètre est passé par valeur (et n'est pas une référence), il n'y a généralement pas beaucoup de différence si le paramètre est déclaré const ou non (à moins qu'il ne contienne un membre de référence - ce n'est pas un problème pour les types intégrés). Si le paramètre est une référence ou un pointeur, il est généralement préférable de protéger la mémoire référencée/pointée, et non le pointeur lui-même (je pense que vous ne pouvez pas faire la référence elle-même, pas que cela compte beaucoup car vous ne pouvez pas changer d'arbitre). . Cela semble une bonne idée de protéger tout ce que vous pouvez en tant que const. Vous pouvez l'omettre sans crainte de se tromper si les paramètres ne sont que des POD (y compris les types intégrés) et qu'il n'y a aucune chance qu'ils changent plus tard (par exemple, dans votre exemple, le paramètre bool).

Je ne connaissais pas la différence de déclaration de fichier .h/.cpp, mais cela a du sens. Au niveau du code machine, rien n'est "const", donc si vous déclarez une fonction (dans le .h) comme non-const, le code est le même que si vous le déclariez comme const (optimisations mises à part). Toutefois, il est utile de demander au compilateur de ne pas modifier la valeur de la variable dans l’implémentation de la fonction (.ccp). Cela peut s'avérer utile dans le cas où vous héritez d'une interface qui permet le changement, mais vous n'avez pas besoin de changer de paramètre pour obtenir les fonctionnalités requises.

1
Attila

Je ne mettrais pas const sur des paramètres comme celui-ci - tout le monde sait déjà qu'un booléen (par opposition à un booléen &) est constant, donc l'ajouter dedans fera penser les gens "attendez, quoi?" ou même que vous passez le paramètre par référence.

0
Khoth

la chose à retenir avec const est qu’il est beaucoup plus facile de rendre les choses constantes au début que d’essayer de les intégrer plus tard.

Utilisez const lorsque vous voulez que quelque chose reste inchangé - c'est un indice supplémentaire qui décrit ce que fait votre fonction et à quoi s'attendre. J'ai vu de nombreuses API C qui pourraient fonctionner avec certaines d'entre elles, notamment celles qui acceptent les c-strings!

Je serais plus enclin à omettre le mot-clé const dans le fichier cpp que l'en-tête, mais comme j'ai tendance à les couper et les coller, ils seraient conservés aux deux endroits. Je ne sais pas pourquoi le compilateur le permet, je suppose que c'est un compilateur. La meilleure pratique est certainement de mettre votre mot clé const dans les deux fichiers.

0
gbjbaanb

Tous les constants dans vos exemples n'ont aucun but. C++ est transmis valeur par défaut, ainsi la fonction obtient des copies de ces entiers et booléens. Même si la fonction les modifie, la copie de l'appelant n'est pas affectée.

Donc, je voudrais éviter les consts supplémentaires parce que

  • Ils sont redudant
  • Ils encombrent le texte
  • Ils m'empêchent de changer la valeur passée dans les cas où cela pourrait être utile ou efficace.
0
AShelly

Il n'y a vraiment aucune raison de créer un paramètre de valeur "const" car la fonction ne peut de toute façon que modifier une copie de la variable.

La raison d'utiliser "const" est si vous passez quelque chose de plus gros (par exemple une structure avec beaucoup de membres) par référence, auquel cas cela garantit que la fonction ne peut pas la modifier; ou plutôt, le compilateur se plaindra si vous essayez de le modifier de manière conventionnelle. Cela empêche sa modification accidentelle.

0
MarkR

Comme les paramètres sont passés par valeur, cela ne fait aucune différence que vous spécifiiez const ou pas du point de vue de la fonction appelante. Cela n'a aucun sens de déclarer des paramètres pass by value en tant que const.

0
siddharth

Le paramètre const est utile uniquement lorsque le paramètre est passé par référence, c'est-à-dire référence ou pointeur. Lorsque le compilateur voit un paramètre const, il s'assure que la variable utilisée dans le paramètre n'est pas modifiée dans le corps de la fonction. Pourquoi voudrait-on créer un paramètre par valeur constant? :-)

0
Yogish Baliga