Utilisation de «const» pour les paramètres de fonction


397

Jusqu'où allez-vous const? Faites-vous simplement des fonctions constlorsque cela est nécessaire ou allez-vous tout le porc et utilisez-le partout? Par exemple, imaginez un simple mutateur qui prend un seul paramètre booléen:

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

Est-ce constvraiment utile? 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 également été surpris d'apprendre que vous pouvez omettre constdes paramètres dans une déclaration de fonction mais 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.


Je ne suis pas d'accord. Le fichier .h doit également avoir les définitions const. Sinon, si les paramètres const 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 const.
selwyn

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

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

1
Même débat qui se passe dans cette question: stackoverflow.com/questions/1554750/…
partiel

5
Je me rends compte que ce message a quelques années, mais en tant que nouveau programmeur, je me posais cette question et je suis tombé sur cette conversation. À mon avis, si une fonction ne doit pas changer une valeur, que ce soit une référence ou une copie de la valeur / objet, elle doit être const. C'est plus sûr, c'est auto-documenté et c'est plus facile à déboguer. Même pour la fonction la plus simple, qui a une instruction, j'utilise toujours const.

Réponses:


187

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 de toute façon la même. C'est probablement un mauvais style de faire beaucoup de choses.

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 qu'il puisse être plus sûr car il signale l'intention dans la fonction. C'est vraiment un appel au jugement. J'ai tendance à utiliser const_iterator quand je boucle sur quelque chose et je n'ai pas l'intention de le modifier, donc je suppose que chacun le sien, tant que l'exactitude de const pour les types de référence est rigoureusement maintenue.


57
Je ne peux pas être d'accord avec la partie "mauvais style". La suppression constdes prototypes de fonction présente l'avantage de ne pas avoir à modifier le fichier d'en-tête si vous décidez de supprimer la constpartie d'implémentation ultérieurement.
Michał Górny

4
"Personnellement, j'ai tendance à ne pas utiliser const sauf pour les paramètres de référence et de pointeur." Peut-être devriez-vous préciser que «j'ai tendance à ne pas utiliser de qualificatifs superflus dans les déclarations de fonctions, mais à utiliser constlà où cela fait une différence utile».
Déduplicateur

3
Je ne suis pas d'accord avec cette réponse. Je penche dans l'autre sens et marque les paramètres constchaque fois que possible; c'est plus expressif. Quand je lis le code de quelqu'un d'autre, j'utilise de petits indicateurs comme celui-ci pour juger du soin qu'ils mettent à écrire leur code à côté de choses comme les nombres magiques, les commentaires et l'utilisation appropriée du pointeur, etc.
Ultimater

4
int getDouble(int a){ ++a; return 2*a; }Essaye ça. Bien sûr, le ++an'a rien à faire là-bas mais on peut le trouver dans une longue fonction écrite par plus d'un programmeur sur une longue période de temps. Je suggère fortement d'écrire int getDouble( const int a ){ //... }qui générera une erreur de compilation lors de la recherche ++a;.
dom_beau

3
Tout dépend de qui a besoin de quelles informations. Vous fournissez le paramètre par valeur afin que l'appelant n'ait besoin de rien savoir sur ce que vous en faites (en interne). Alors écrivez class Foo { int multiply(int a, int b) const; }dans votre en-tête. Dans votre mise en œuvre, vous vous souciez de pouvoir promettre de ne pas modifier aet est bdonc int Foo::multiply(const int a, const int b) const { }logique ici. (Sidenote: l'appelant et l'implémentation se soucient du fait que la fonction ne modifie pas son Fooobjet, donc le const à la fin de sa déclaration)
CharonX

415

"const est inutile lorsque l'argument est passé par valeur car 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 gens qui travaillent dessus et que vos fonctions ne sont pas triviales, vous devez marquer "const" tout ce que vous pouvez. Lorsque vous écrivez du code de force industrielle, vous devez toujours supposer que vos collègues sont des psychopathes qui essaient de vous aider de toutes les manières possibles (d'autant plus que c'est souvent vous-même à l'avenir).

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


41
Entièrement d'accord. Il s'agit de communiquer avec les gens et de restreindre ce qui pourrait être fait avec une variable à ce qui devrait être fait.
Len Holgate

19
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 passage par valeur.
tonylo

26
J'ai voté pour celui-ci. La déclaration du paramètre «const» ajoute des informations sémantiques au paramètre. Ils mettent en évidence ce que l'auteur original du code avait prévu et cela facilitera la maintenance du code au fil du temps.
Richard Corden

13
@tonylo: vous vous méprenez. Il s'agit de marquer une variable locale comme const à l'intérieur d'un bloc de code (qui se trouve être une fonction). Je ferais la même chose pour n'importe quelle variable locale. Il est orthogonal d'avoir une API const-correcte, ce qui est en effet également important.
rlerallut

56
Et il peut attraper des bogues à l' intérieur de la fonction - si vous savez qu'un paramètre ne doit pas être modifié, le déclarer const signifie que le compilateur vous dira si vous le modifiez accidentellement.
Adrian

157

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 gâchis 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 également des arguments de valeur de qualification de const, car ce ne sont que de fantastiques variables locales initialisées par l'appelant.

Ah, je souhaite que les variables soient const par défaut et qu'elles mutables pour les variables non const :)


4
"Je souhaite que les variables soient constées par défaut" - un oxymore ?? 8-) Sérieusement, comment tout "consting" vous aide à démêler le code? Si l'auteur original a changé un argument supposé constant, comment savez-vous que le var était censé être une constante? De plus, la grande majorité des variables (sans argument) sont censées être ... des variables. Le compilateur devrait donc s'arrêter très peu de temps après le début du processus, non?
ysap

9
@ysap, 1. Marquer const autant que possible me permet de voir quelles pièces se déplacent et lesquelles ne le sont pas. D'après mon expérience, de nombreux habitants sont de facto const, et non l'inverse. 2. "Const variable" / "Immutable variable" peut sonner comme oxymore, mais est une pratique courante dans les langages fonctionnels, ainsi que dans certains langages non fonctionnels; voir Rust par exemple: doc.rust-lang.org/book/variable-bindings.html
Constantin

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

10
Les variables sont " const" par défaut dans Rust :)
phoenix

Les variables ne doivent pas nécessairement être assignables pour leur permettre de varier; la valeur avec laquelle ils sont initialisés peut également varier lors de l'exécution.
Sphynx

80

Les deux lignes suivantes sont fonctionnellement équivalentes:

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

De toute évidence, vous ne pourrez pas modifier ale corps defoo si c'est défini de la deuxième façon, mais il n'y a pas de différence de l'extérieur.

Ce constqui 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 grand paramètre, peut-être une structure de données de taille gigaoctet, sans la copier. En outre, il dit à l'appelant: "Foo ne changera pas le contenu de ce paramètre." La transmission d'une référence const permet également au compilateur de prendre certaines décisions en matière de performances.

*: A moins qu'il ne rejette la constance, mais c'est un autre post.


3
Ce n'est pas de cela qu'il s'agit; 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 const dans votre exemple de pointeur; c'est ce que le paramètre indique.
tml

> La transmission d'une référence const permet également au compilateur de prendre certaines décisions en matière de performances. erreur classique - le compilateur doit déterminer lui-même la const-ness, le mot clé const n'y aide pas grâce à l'aliasing du pointeur et const_cast
jheriko

73

Les constats superflus sont mauvais du point de vue de l'API:

Mettre des constants superflus supplémentaires dans votre code pour les paramètres de type intrinsèque transmis par la valeur encombre votre API sans faire de 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 quand ils ne sont pas nécessaires, c'est comme « pleurer le loup », finalement les gens commenceront à ignorer «const» parce qu'il est partout et ne veut rien dire la plupart du temps.

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

Essayons donc de mettre const partout où nous 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 et plus longue et plus difficile à 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 DANGEREUSE!

Pourquoi?

Une mauvaise lecture rapide du premier paramètre char * const bufferpourrait vous faire penser qu'il ne modifiera pas la mémoire dans le tampon de données qui est passé - cependant, ce n'est pas vrai!Un «const» superflu peut conduire à des hypothèses dangereuses et incorrectes sur votre API lorsqu'il est analysé ou mal lu rapidement.


Les constats superflus sont également mauvais 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, alors 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 n'apporte aucun avantage à votre appelant et limite seulement votre mise en œuvre?

Les deux sont des implémentations parfaitement valides de la même fonction, donc tout ce que vous avez fait est lié inutilement une main derrière votre dos.

De plus, c'est une promesse très superficielle qui est facilement (et juridiquement contournable).

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 de toute façon, même si j'ai 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 constantes superflues ne valent rien de plus qu'une promesse d'un méchant de film.


Mais la capacité de mentir devient encore pire:

J'ai été informé que vous pouvez ne pas faire correspondre const dans l'en-tête (déclaration) et le code (définition) en utilisant un faux const. Les défenseurs const-happy affirment que c'est une bonne chose car cela vous permet de mettre const uniquement 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 ne pouvez mettre une constante parasite que dans la déclaration et l'ignorer dans la définition. Cela ne fait que constuire superflu 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
}

En fait, tout ce qui est superflu est de rendre le code de l'implémentateur moins lisible en l'obligeant à utiliser une autre copie locale ou une fonction d'encapsuleur quand il veut changer la variable ou passer la variable par référence non const.

Regardez cet exemple. Quel est le plus lisible? Est-il évident que la seule raison de la variable supplémentaire dans la deuxième fonction est que certains concepteurs d'API ont 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;
    }
}

J'espère que nous avons appris quelque chose ici. La constance superflue est une horreur encombrante, un bourrin ennuyeux, une promesse superficielle et dénuée de sens, un obstacle inutile, et conduit parfois à des erreurs très dangereuses.


9
Pourquoi les downvotes? C'est beaucoup plus utile si vous laissez un bref commentaire sur un downvote.
Adisak

7
L'intérêt de l'utilisation de l'argument const est de faire échouer la ligne marquée (plist = pnext). Il s'agit d'une mesure de sécurité raisonnable pour conserver l'argument de la fonction immuable. Je suis d'accord avec votre point de vue qu'ils sont mauvais dans les déclarations de fonctions (car ils sont superflus), mais ils peuvent servir leurs objectifs dans le bloc d'implémentation.
touko

23
@Adisak Je ne vois rien de mal à votre réponse, en soi, mais il semble d'après vos commentaires que vous manquez un point important. La définition / implémentation de la fonction ne fait pas partie de l'API, qui n'est que la déclaration de fonction . Comme vous l'avez dit, déclarer des fonctions avec des paramètres const est inutile et ajoute de l'encombrement. Cependant, les utilisateurs de l'API peuvent ne jamais avoir besoin de voir son implémentation. Pendant ce temps, l'implémenteur peut décider de ne qualifier certains paramètres de la définition de fonction que pour la clarté, ce qui est parfaitement correct.
jw013

17
@ jw013 est correct void foo(int)et a void foo(const int)exactement la même fonction, pas les surcharges. ideone.com/npN4W4 ideone.com/tZav9R La const ici n'est qu'un détail d'implémentation du corps de la fonction, et n'a aucun effet sur la résolution de surcharge. Laissez const hors de la déclaration, pour une API plus sûre et plus nette, mais mettez const dans la définition , si vous avez l'intention de ne pas modifier la valeur copiée.
Oktalist

3
@Adisak Je sais que c'est ancien, mais je pense que l'utilisation correcte d'une API publique serait l'inverse. De cette façon, les développeurs travaillant sur les composants internes ne font pas d'erreurs comme pi++quand ils ne sont pas censés le faire.
CoffeeandCode

39

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

8
Exactement mes pensées.
Constantin

24
La compatibilité avec C est trop importante, du moins pour les personnes qui conçoivent C ++, pour même y penser.

4
Intéressant, je n'y avais jamais pensé.
Dan

6
De même, unsignedaurait dû être la valeur par défaut en C ++. Comme ça: int i = 5; // i is unsignedet signed int i = 5; // i is signed.
hkBattousai

25

Quand j'ai codé C ++ pour gagner ma vie, j'ai consterné tout ce que je pouvais. L'utilisation de const est un excellent moyen d'aider le compilateur à vous aider. Par exemple, la constance des valeurs de retour de votre méthode peut vous éviter des fautes de frappe telles que:

foo() = 42

quand tu voulais dire:

foo() == 42

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

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

Le compilateur vous laissera volontiers attribuer une valeur au temporaire anonyme renvoyé par l'appel de fonction. Faire const:

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

Élimine cette possibilité.


6
Avec quel compilateur cela fonctionnait-il? GCC donne une erreur lors de la compilation foo() = 42: erreur: lvalue requise comme opérande gauche de l'affectation
gavrie

C'est tout simplement incorrect. foo () = 42 est identique à 2 = 3, c'est-à-dire une erreur de compilation. Et retourner un const est complètement inutile. Il ne fait rien pour un type intégré.
Josh

2
J'ai rencontré cette utilisation de const et je peux vous dire qu'en fin de compte, cela produit beaucoup plus de tracas que d'avantages. Astuce: const int foo()est d'un type différent de int foo(), ce qui vous pose de gros problèmes si vous utilisez des choses comme des pointeurs de fonction, des systèmes de signal / slot ou boost :: bind.
Mephane

2
J'ai corrigé le code pour inclure la valeur de retour de référence.
Avdi

N'est-il pas const int& foo()effectivement le même que int foo(), en raison de l'optimisation de la valeur de retour?
Zantier

15

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

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


1
Herb Sutter est un gars vraiment intelligent :-) Vaut vraiment la peine d'être lu et je suis d'accord avec TOUS ses points.
Adisak

2
Bon bon article mais je ne suis pas d'accord avec lui sur les arguments. Je les fais const aussi parce qu'elles sont comme des variables, et je ne veux jamais que quiconque modifie mes arguments.
QBziZ

9

Je dis const vos paramètres de valeur.

Considérez cette fonction de buggy:

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

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


2
une autre façon utilise if (0 == nombre) ... else ...;
Johannes Schaub - litb

5
@ ChrisHuang-Leaver Horrible ce n'est pas le cas, si vous parlez comme Yoda vous le faites: stackoverflow.com/a/2430307/210916
MPelletier

GCC / Clang -Wall vous donne -Wparentheses, ce qui vous oblige à le faire "si ((nombre = 0))" si c'est bien ce que vous aviez l'intention de faire. Ce qui fonctionne bien comme substitut à être Yoda.
Jetski S-type

8

J'utilise const sur des paramètres de fonction qui sont des références (ou des pointeurs) qui ne sont que des données [in] et ne seront pas modifiées par la fonction. Signification, lorsque le but d'utiliser une référence est d'éviter la copie de données et de ne pas permettre de changer le paramètre passé.

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

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


6

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 voulez.

Ce que je voulais dire était

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

et si j'avais mis un constentre Bar *et p, le compilateur me l'aurait dit.


4
Je vérifierais immédiatement une référence sur la priorité des opérateurs lorsque je suis sur le point de mélanger autant d'opérateurs (si je ne connais pas déjà 100%), donc IMO ce n'est pas un problème.
mk12

5

Ah, difficile. D'un côté, une déclaration est un contrat et cela n'a vraiment aucun sens de passer un argument const par valeur. D'un autre côté, si vous regardez l'implémentation de la fonction, vous donnez au compilateur plus de chances d'optimiser si vous déclarez un argument constant.


5

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

const doit ê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;}

Il s'agit d'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.


5

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

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

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

Pour moi, la valeur indique clairement que les valeurs des paramètres de 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 / d'espace d'avoir le «const», car il est généralement assez évident que les arguments ne sont pas modifiés par la fonction.

Cependant, pour une fonction plus grande, c'est une forme de documentation d'implémentation, et elle est 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 parce que j'ai raté un endroit où l'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.


4

Peut-être que ce ne sera pas un argument valable. mais si nous incrémentons la valeur d'une variable const à l'intérieur d'un compilateur de fonctions, nous obtiendrons une erreur: " erreur: incrémentation du paramètre en lecture seule ". ce qui signifie que nous pouvons utiliser le mot clé const comme un moyen d'empêcher de modifier accidentellement nos variables à l'intérieur des fonctions (que nous ne sommes pas censés / en lecture seule). donc si nous l'avons accidentellement fait au moment de la compilation, le compilateur nous le fera savoir. c'est particulièrement important si vous n'êtes pas le seul à travailler sur ce projet.


3

J'ai tendance à utiliser const autant que possible. (Ou tout autre mot clé approprié pour la langue cible.) Je le fais uniquement parce qu'il permet au compilateur de faire des optimisations supplémentaires qu'il ne pourrait pas faire autrement. Comme je n'ai aucune idée de ce que peuvent être ces optimisations, je le fais toujours, même là où 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 le modifie pas de toute façon, donc je peux passer par référence et enregistrer quelques cycles d'horloge." Je ne pense pas que cela ferait jamais une telle chose, car cela change la signature de la fonction, mais cela fait le point. Peut-être que cela fait une manipulation de pile différente ou quelque chose ... 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 des bagages supplémentaires, avec l'idée de const-correctness, donc cela devient encore plus important.


Bien que cela puisse aider dans certains cas, je soupçonne que la possibilité de promouvoir des optimisations est considérablement surestimée en tant qu'avantage const. Il s'agit plutôt d'indiquer l'intention au sein de l'implémentation et de rattraper les penseurs plus tard (incrémenter accidentellement la mauvaise variable locale, car ce n'était pas le cas const). Parallèlement, j'ajouterais également que les compilateurs sont les bienvenus pour modifier les signatures de fonction, dans le sens où les fonctions peuvent être intégrées, et une fois intégrées, la façon dont elles fonctionnent peut être modifiée; l'ajout ou la suppression de références, la création de littéraux de «variables», etc. sont tous dans la règle comme si
underscore_d

3

1. Meilleure réponse basée sur mon évaluation:

La réponse de @Adisak est la meilleure réponse ici basée sur mon évaluation. Notez que cette réponse est en partie la meilleure car elle est également la plus bien sauvegardée avec de vrais exemples de code , en plus d'utiliser une logique saine et bien pensée.

2. Mes propres mots (en accord avec la meilleure réponse):

  1. Pour la valeur de passage, il n'y a aucun avantage à ajouter const . Il ne fait que:
    1. limiter l'implémenteur à avoir à faire une copie chaque fois qu'il veut changer un paramètre d'entrée dans le code source (ce changement n'aurait de toute façon aucun effet secondaire puisque ce qui est passé est déjà une copie car il est transmis par valeur). Et fréquemment, la modification d'un paramètre d'entrée transmis par valeur est utilisée pour implémenter la fonction, ajoutant ainsiconst partout peut entraver cela.
    2. et l'ajout constencombre inutilement le code avec consts partout, détournant l'attention des consts qui sont vraiment nécessaires pour avoir un code sûr.
  2. Cependant, lorsqu'il s'agit de pointeurs ou de références , il constest extrêmement important en cas de besoin, et doit être utilisé, car il empêche les effets secondaires indésirables avec des changements persistants en dehors de la fonction, et donc chaque pointeur ou référence doit utiliser constlorsque le parm est une entrée uniquement, pas une sortie. L'utilisation const uniquement de paramètres passés par référence ou par pointeur a l'avantage supplémentaire de rendre vraiment évident quels paramètres sont des pointeurs ou des références. C’est une chose de plus que de s’exprimer et de dire "Attention!const côté de lui est une référence ou un pointeur!".
  3. Ce que j'ai décrit ci-dessus a souvent été le consensus atteint dans les organisations professionnelles de logiciels dans lesquelles j'ai travaillé et a été considéré comme la meilleure pratique. Parfois même, la règle a été stricte: "n'utilisez jamais const sur des paramètres passés par valeur, mais utilisez-le toujours sur des paramètres passés par référence ou par pointeur si ce ne sont que des entrées."

3. Les mots de Google (d'accord avec moi et la meilleure réponse):

(Extrait du " Google C ++ Style Guide ")

Pour un paramètre de fonction passé par valeur, const n'a aucun effet sur l'appelant, il n'est donc pas recommandé dans les déclarations de fonction. Voir TotW # 109 .

L'utilisation de const sur les variables locales n'est ni encouragée ni découragée.

Source: section "Utilisation de const" du Guide de style Google C ++: https://google.github.io/styleguide/cppguide.html#Use_of_const . Il s'agit en fait d'une section très utile, alors lisez toute la section.

Notez que "TotW # 109" signifie "Astuce de la semaine # 109: Significatif constdans les déclarations de fonction" , et est également une lecture utile. Il est plus informatif et moins normatif sur ce qu'il faut faire, et basé sur le contexte avant la règle du guide de style Google C ++ constcitée ci-dessus, mais en raison de la clarté qu'il a fournie, la constrègle citée ci-dessus a été ajoutée au Google C ++ Guide de style.

Notez également que même si je cite le guide de style Google C ++ ici pour défendre ma position, cela ne signifie PAS que je suis toujours le guide ou que je recommande toujours de le suivre. Certaines des choses qu'ils recommandent sont tout simplement étranges, comme leur kDaysInAWeekconvention de dénomination -style pour les "noms constants" . Cependant, il est toujours utile et pertinent de souligner quand l'une des sociétés techniques et logicielles les plus performantes et les plus influentes au monde utilise la même justification que moi et d'autres comme @Adisak pour étayer nos points de vue sur cette question.

4. Linter de Clang, clang-tidy , a quelques options pour cela:

R. Il est également intéressant de noter que le linter de Clang, clang-tidya une option, readability-avoid-const-params-in-decls, décrite ici , pour soutenir l' application dans une base de code non en utilisant constdes paramètres de fonction passe par valeur :

Vérifie si une déclaration de fonction a des paramètres qui sont const de niveau supérieur.

Les valeurs const dans les déclarations n'affectent pas la signature d'une fonction, elles ne doivent donc pas y être placées.

Exemples:

void f(const string);   // Bad: const is top level.
void f(const string&);  // Good: const is not top level.

Et voici deux autres exemples que j'ajoute moi-même pour être complet et clair:

void f(char * const c_string);   // Bad: const is top level. [This makes the _pointer itself_, NOT what it points to, const]
void f(const char * c_string);   // Good: const is not top level. [This makes what is being _pointed to_ const]

B. Il a également cette option: readability-const-return-type- https://clang.llvm.org/extra/clang-tidy/checks/readability-const-return-type.html

5. Mon approche pragmatique de la façon dont je rédigerais un guide de style sur la question:

Je voudrais simplement copier et coller ceci dans mon guide de style:

[COPY / PASTE START]

  1. Utilisez toujours const les paramètres de fonction passés par référence ou par pointeur lorsque leur contenu (vers quoi ils pointent) ne doit PAS être modifié. De cette façon, il devient évident lorsqu'une variable passée par référence ou pointeur EST censée être modifiée, car elle manquera const. Dans ce cas d'utilisation, constprévient les effets secondaires accidentels en dehors de la fonction.
  2. Il n'est pas recommandé d'utiliser constsur les paramètres de fonction passés par valeur, car constn'a aucun effet sur l'appelant: même si la variable est modifiée dans la fonction, il n'y aura pas d'effets secondaires en dehors de la fonction. Voir les ressources suivantes pour une justification et des informations supplémentaires:
    1. "Guide de style Google C ++" Section "Utilisation de const"
    2. "Astuce de la semaine n ° 109: des constdéclarations de fonctions significatives "
    3. Réponse d'Adisak Stack Overflow sur "Utilisation de 'const' pour les paramètres de fonction"
  3. " N'utilisez jamais de niveau supérieur const[c'est -à- dire: constsur des paramètres passés par valeur ] sur des paramètres de fonction dans des déclarations qui ne sont pas des définitions (et faites attention à ne pas copier / coller un sens const). Il est vide de sens et ignoré par le compilateur, c'est du bruit visuel , et cela pourrait induire les lecteurs en erreur "( https://abseil.io/tips/109 , je souligne).
    1. Les seuls constqualificatifs qui ont un effet sur la compilation sont ceux placés dans la définition de fonction, PAS ceux dans une déclaration avant de la fonction, comme dans une déclaration de fonction (méthode) dans un fichier d'en-tête.
  4. N'utilisez jamais de niveau supérieur const[c'est -à- dire: constsur des variables passées par valeur ] sur des valeurs renvoyées par une fonction.
  5. L'utilisation constde pointeurs ou de références retournés par une fonction dépend de l'implémenteur , car elle est parfois utile.
  6. À FAIRE: appliquer certaines des clang-tidyoptions ci-dessus avec les options suivantes :
    1. https://clang.llvm.org/extra/clang-tidy/checks/readability-avoid-const-params-in-decls.html
    2. https://clang.llvm.org/extra/clang-tidy/checks/readability-const-return-type.html

Voici quelques exemples de code pour illustrer les constrègles décrites ci-dessus:

constExemples de paramètres:
(certains sont empruntés d' ici )

void f(const std::string);   // Bad: const is top level.
void f(const std::string&);  // Good: const is not top level.

void f(char * const c_string);   // Bad: const is top level. [This makes the _pointer itself_, NOT what it points to, const]
void f(const char * c_string);   // Good: const is not top level. [This makes what is being _pointed to_ const]

constExemples de type de retour:
(certains sont empruntés d' ici )

// BAD--do not do this:
const int foo();
const Clazz foo();
Clazz *const foo();

// OK--up to the implementer:
const int* foo();
const int& foo();
const Clazz* foo();

[FIN COPIE / COLLAGE]


2

Dans le cas que vous mentionnez, cela n'affecte pas les appelants de votre API, c'est pourquoi cela n'est pas courant (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 ne sont pas si grands étant donné que cela n'affecte pas votre API et qu'il ajoute de la frappe, donc ce n'est généralement pas fait.


2

Je n'utilise pas const pour les paramètres à valeur transmise. 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 cela au fur et à mesure, sinon vous pourriez vous retrouver avec beaucoup de const_cast <> ou vous pourriez constater que le marquage d'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 également 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 "parties mobiles".



2

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 des classes.


2

Résumer:

  • "Normalement, la valeur de passage const est inutile et trompeuse au mieux." À partir de GOTW006
  • Mais vous pouvez les ajouter dans le .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 assez bon pour la bibliothèque standard est bon pour moi.

2
"Ce qui est assez bon pour la bibliothèque standard est bon pour moi" n'est pas toujours vrai. Par exemple, la bibliothèque standard utilise des noms de variables moches comme _Tmptout le temps - vous ne voulez pas cela (en fait, vous n'êtes pas autorisé à les utiliser).
anatolyg

1
@anatolyg c'est un détail d'implémentation
Fernando Pelliccioni

2
OK, les noms de variables et les types qualifiés const dans les listes d'arguments sont des détails d'implémentation. Ce que je veux dire, c'est que l'implémentation de la bibliothèque standard n'est parfois pas bonne. Parfois, vous pouvez (et devriez) faire mieux. Quand le code de la bibliothèque standard a-t-il été écrit - il y a 10 ans? Il y a 5 ans (certaines parties les plus récentes)? Nous pouvons écrire un meilleur code aujourd'hui.
anatolyg

1

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 que le paramètre soit déclaré const ou non (sauf s'il contient un membre de référence - 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, pas le pointeur lui-même (je pense que vous ne pouvez pas faire de la référence elle-même une const, pas que cela ait beaucoup d'importance car vous ne pouvez pas changer l'arbitre) . Il semble une bonne idée de protéger tout ce que vous pouvez en tant que const. Vous pouvez l'omettre sans craindre de faire une erreur 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 loin sur la route (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) non-const, le code est le même que si vous la déclarez const (optimisations mises à part). Cependant, cela vous aide à enrôler le compilateur pour ne pas modifier la valeur de la variable dans l'implémentation de la fonction (.ccp). Cela peut être 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 la fonctionnalité requise.


0

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


4
parfois, vous voudriez passer un objet par référence (pour des raisons de performances) mais pas le changer, donc const est alors obligatoire. Conserver tous ces paramètres - même bools - const serait alors une bonne pratique, facilitant la lecture de votre code.
gbjbaanb

0

la chose à retenir avec const est qu'il est beaucoup plus facile de rendre les choses const dès le début, que d'essayer de les mettre 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 faire avec certaines d'entre elles, en particulier celles qui acceptent les chaînes C!

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 + coller, ils seraient conservés aux deux endroits. Je ne sais pas pourquoi le compilateur permet cela, je suppose que c'est une chose de compilateur. La meilleure pratique est certainement de mettre votre mot-clé const dans les deux fichiers.


Je ne comprends pas du tout. Pourquoi auriez-vous tendance à l'omettre dans le fichier cpp (définition de fonction)? C'est là que cela signifie réellement quelque chose et peut détecter des erreurs. Pourquoi pensez-vous qu'il est préférable de mettre const aux deux endroits? Dans le fichier d'en-tête (déclaration de fonction), cela ne signifie rien et encombre l'API. Peut-être y a-t-il une petite valeur à avoir la même apparence que le def et le defn, mais il me semble que c'est un avantage vraiment mineur par rapport au problème d'encombrement de l'API.
Don Hatch

@DonHatch 8 ans plus tard, wow. Quoi qu'il en soit, comme l'OP l'a dit "J'ai également été surpris d'apprendre que vous pouvez omettre const des paramètres dans une déclaration de fonction mais pouvez l'inclure dans la définition de fonction".
gbjbaanb

0

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 grand (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 la manière conventionnelle. Cela l'empêche d'être modifié accidentellement.


0

Le paramètre const n'est utile que lorsque le paramètre est transmis par référence, c'est-à-dire par référence ou par 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 rendre constant un paramètre de valeur? :-)


Pour de nombreuses raisons. La définition d'un paramètre par valeur constindique clairement: «Je n'ai pas besoin de modifier cela, donc je le déclare. Si j'essaye de le modifier plus tard, donnez-moi une erreur au moment de la compilation afin que je puisse corriger mon erreur ou annuler le marquage const. ' C'est donc une question d'hygiène et de sécurité du code. Pour tout ce qu'il faut pour ajouter aux fichiers d'implémentation, cela devrait être quelque chose que les gens font comme un pur réflexe, l'OMI.
underscore_d

0

Étant donné que les paramètres sont transmis par valeur, cela ne fait aucune différence si vous spécifiez const ou non du point de vue de la fonction appelante. Cela n'a en fait aucun sens de déclarer les paramètres de passage par valeur comme const.


0

Tous les consts dans vos exemples n'ont aucun sens. C ++ est transmis par valeur par défaut, donc 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.

J'éviterais donc des consts supplémentaires parce que

  • Ils sont redudant
  • Ils encombrent le texte
  • Ils m'empêchent de modifier la valeur transmise dans les cas où cela pourrait être utile ou efficace.

-1

Je sais que la question est "un peu" dépassée, mais au fur et à mesure que je l'ai rencontrée, quelqu'un d'autre peut aussi le faire à l'avenir ... ... je doute encore que le pauvre garçon énumérera ici pour lire mon commentaire :)

Il me semble que nous sommes encore trop cantonnés à une pensée de type C. Dans le paradigme OOP, nous jouons avec des objets, pas avec des types. L'objet const peut être conceptuellement différent d'un objet non const, en particulier dans le sens de logique-const (contrairement à bitwise-const). Ainsi, même si l'exactitude constante des paramètres de fonction est (peut-être) une prudence excessive dans le cas des POD, elle ne l'est pas dans le cas des objets. Si une fonction fonctionne avec un objet const, elle doit le dire. Considérez l'extrait de code suivant

#include <iostream>

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
class SharedBuffer {
private:

  int fakeData;

  int const & Get_(int i) const
  {

    std::cout << "Accessing buffer element" << std::endl;
    return fakeData;

  }

public:

  int & operator[](int i)
  {

    Unique();
    return const_cast<int &>(Get_(i));

  }

  int const & operator[](int i) const
  {

    return Get_(i);

  }

  void Unique()
  {

    std::cout << "Making buffer unique (expensive operation)" << std::endl;

  }

};

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
void NonConstF(SharedBuffer x)
{

  x[0] = 1;

}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
void ConstF(const SharedBuffer x)
{

  int q = x[0];

}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
int main()
{

  SharedBuffer x;

  NonConstF(x);

  std::cout << std::endl;

  ConstF(x);

  return 0;

}

ps.: vous pouvez affirmer que la référence (const) serait plus appropriée ici et vous donne le même comportement. Et bien. Juste donner une image différente de ce que je pouvais voir ailleurs ...

En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.