172 votes

Pourquoi utiliser des préfixes sur les variables membres des classes C++ ?

Une grande partie du code C++ utilise des conventions syntaxiques pour marquer les variables membres. Les exemples les plus courants sont

  • m_ nom du membre pour les membres publics (lorsque des membres publics sont utilisés)
  • _ nom du membre pour les membres privés ou tous les membres

D'autres tentent de faire respecter la loi en utilisant cette-> membre chaque fois qu'une variable membre est utilisée.

D'après mon expérience, la plupart des grandes bases de code ne parviennent pas à appliquer ces règles de manière cohérente.

Dans les autres langages, ces conventions sont beaucoup moins répandues. Je ne les vois qu'occasionnellement dans du code Java ou C#. Je pense ne jamais l'avoir vu dans du code Ruby ou Python. Ainsi, il semble y avoir une tendance avec les langages plus modernes à ne pas utiliser de balisage spécial pour les variables membres.

Cette convention est-elle encore utile aujourd'hui en C++ ou est-elle simplement un anachronisme. D'autant plus qu'elle est utilisée de manière si incohérente entre les bibliothèques. Les autres langages n'ont-ils pas montré que l'on peut se passer des préfixes des membres ?

21 votes

Je le préfère ; dans les bases de code complexes, il peut être important de savoir quelles variables sont locales et lesquelles ne le sont pas. J'utilise généralement le préfixe plutôt que de forcer this-> qui, à mon avis, représente beaucoup de saisie supplémentaire et est facultatif (alors que le nommage vous forcera à le faire).

6 votes

Vous ne l'avez jamais vu en Ruby à cause de @ pour attribut, et de l'idiome qui consiste à générer des accesseurs de préférence à l'utilisation directe des attributs.

6 votes

Selon PEP 8 Les variables membres non publiques doivent être préfixées par un trait de soulignement en Python (exemple : self._something = 1 ).

262voto

Jason Williams Points 31901

Je suis tout à fait favorable à les préfixes bien faits .

Je pense que la notation hongroise (système) est responsable de la plupart des "mauvaises critiques" dont les préfixes font l'objet.

Cette notation est largement inutile dans les langages fortement typés, par exemple en C++ "lpsz" pour vous dire que votre chaîne est un pointeur long vers une chaîne à terminaison nulle, alors que : l'architecture segmentée est de l'histoire ancienne, les chaînes C++ sont par convention des pointeurs vers des tableaux de chars à terminaison nulle, et il n'est pas vraiment difficile de savoir que "nomclient" est une chaîne !

Cependant, j'utilise des préfixes pour spécifier le nom de l'utilisateur. utilisation d'une variable (essentiellement "Apps hongrois", bien que je préfère éviter le terme hongrois en raison de son association mauvaise et injuste avec le système hongrois), et il s'agit d'une fonction très pratique de l'outil gain de temps y Réduction des bogues approche.

J'utilise :

  • m pour les membres
  • c pour les constantes/readonlys
  • p pour pointeur (et pp pour pointeur à pointeur)
  • v pour volatile
  • s pour le statique
  • i pour les index et les itérateurs
  • e pour les événements

Où je souhaite faire le type clair, j'utilise des suffixes standard (par exemple, List, ComboBox, etc.).

Cela permet au programmeur de prendre conscience de la utilisation de la variable à chaque fois qu'ils la voient/utilisent. Le cas le plus important est sans doute "p" pour pointeur (parce que l'usage change de var. à var-> et qu'il faut être beaucoup plus prudent avec les pointeurs - NULLs, arithmétique des pointeurs, etc.

Par exemple, vous pouvez utiliser le même nom de variable de plusieurs façons dans une même fonction : (ici un exemple en C++, mais il s'applique également à de nombreux langages)

MyClass::MyClass(int numItems)
{
    mNumItems = numItems;
    for (int iItem = 0; iItem < mNumItems; iItem++)
    {
        Item *pItem = new Item();
        itemList[iItem] = pItem;
    }
}

Vous pouvez voir ici :

  • Pas de confusion entre membre et paramètre
  • Pas de confusion entre index/iterator et items
  • Utilisation d'un ensemble de variables clairement liées (liste d'éléments, pointeur et index) qui évitent les nombreux pièges des noms génériques (vagues) comme "count", "index".
  • Les préfixes réduisent la saisie (ils sont plus courts et fonctionnent mieux avec l'autocomplétion) que des alternatives comme "itemIndex" et "itemPtr".

Un autre avantage des itérateurs "iName" est que je n'indexe jamais un tableau avec un mauvais indice, et si je copie une boucle à l'intérieur d'une autre boucle, je n'ai pas besoin de remanier l'une des variables d'indice de la boucle.

Comparez cet exemple simple et irréaliste :

for (int i = 0; i < 100; i++)
    for (int j = 0; j < 5; j++)
        list[i].score += other[j].score;

(ce qui est difficile à lire et conduit souvent à l'utilisation de "i" là où "j" était prévu)

avec :

for (int iCompany = 0; iCompany < numCompanies; iCompany++)
    for (int iUser = 0; iUser < numUsers; iUser++)
       companyList[iCompany].score += userList[iUser].score;

(ce qui est beaucoup plus lisible, et supprime toute confusion sur l'indexation. Avec l'auto-complétion dans les IDE modernes, c'est également rapide et facile à taper)

L'avantage suivant est que les extraits de code ne nécessitent aucun contexte pour être compris. Je peux copier deux lignes de code dans un courrier électronique ou un document, et toute personne lisant ce bout de code peut faire la différence entre tous les membres, constantes, pointeurs, index, etc. Je n'ai pas besoin d'ajouter "oh, et faites attention parce que 'data' est un pointeur sur un pointeur", parce qu'il s'appelle 'ppData'.

Et pour la même raison, je n'ai pas besoin de quitter des yeux une ligne de code pour la comprendre. Je n'ai pas besoin de chercher dans le code pour savoir si "data" est un local, un paramètre, un membre ou une constante. Je n'ai pas besoin de déplacer ma main vers la souris pour placer le pointeur sur "data" et attendre qu'une info-bulle (qui parfois n'apparaît jamais) apparaisse. Pour que les programmeurs puissent lire et comprendre le code de manière significative plus rapidement, car ils ne perdent pas de temps à chercher de haut en bas ou à attendre.

(Si vous ne pensez pas que vous perdez du temps à chercher de haut en bas pour résoudre des problèmes, trouvez un code que vous avez écrit il y a un an et que vous n'avez pas regardé depuis. depuis. Ouvrez le fichier et passez à peu près à la moitié du code sans le lire. Voyez jusqu'où vous pouvez lire à partir de ce point avant de ne plus savoir si quelque chose est un membre, un paramètre ou un local. Maintenant, sautez à un autre endroit aléatoire... C'est ce que nous faisons tous à longueur de journée quand nous sommes célibataires le code de quelqu'un d'autre ou que nous essayons de comprendre comment appeler leur fonction)

Le préfixe 'm' permet également d'éviter la notation "this->", qui est (à mon avis) laide et verbeuse, et l'incohérence qu'elle garantit (même si vous faites attention, vous vous retrouverez généralement avec un mélange de "this->data" et de "data" dans la même classe, parce que rien n'impose une orthographe cohérente du nom).

La notation "this" est destinée à résoudre ambiguïté - mais pourquoi quelqu'un écrirait-il délibérément un code qui peut être ambigu ? Ambiguïté sera conduisent tôt ou tard à un bogue. Et dans certains langages, "ceci" ne peut pas être utilisé pour les membres statiques, ce qui vous oblige à introduire des "cas spéciaux" dans votre style de codage. Je préfère avoir une seule règle de codage simple qui s'applique partout - explicite, sans ambiguïté et cohérente.

Le dernier avantage majeur concerne l'Intellisense et l'autocomplétion. Essayez d'utiliser Intellisense sur un formulaire Windows pour trouver un événement - vous devez parcourir des centaines de méthodes de classe de base mystérieuses que vous n'aurez jamais besoin d'appeler pour trouver les événements. Mais si chaque événement avait un préfixe "e", ils seraient automatiquement listés dans un groupe sous "e". Ainsi, le préfixe fonctionne pour regrouper les membres, les constantes, les événements, etc. dans la liste intellisense, ce qui rend beaucoup plus rapide et plus facile de trouver les noms que vous voulez. (Habituellement, une méthode peut avoir environ 20-50 valeurs (locales, params, membres, consts, événements) qui sont accessibles dans sa portée. Mais après avoir tapé le préfixe (je veux utiliser un index maintenant, donc je tape 'i...'), on me présente seulement 2-5 options d'auto-complétion. La "saisie supplémentaire" que les gens attribuent aux préfixes et aux noms significatifs réduit considérablement l'espace de recherche et accélère sensiblement la vitesse de développement).

Je suis un programmeur paresseux, et la convention ci-dessus me permet d'économiser beaucoup de travail. Je peux coder plus rapidement et je fais beaucoup moins d'erreurs car je sais comment chaque variable doit être utilisée.


Arguments contre

Alors, quels sont les inconvénients ? Les arguments typiques contre les préfixes sont :

  • "Les systèmes de préfixes sont mauvais/diaboliques". . Je suis d'accord pour dire que "m_lpsz" et ses semblables sont mal conçus et totalement inutiles. C'est pourquoi je vous conseille d'utiliser une notation bien conçue pour répondre à vos besoins, plutôt que de copier quelque chose qui n'est pas adapté à votre contexte. (Utilisez le bon outil pour le bon travail).

  • "Si je change l'usage de quelque chose, je dois le renommer". . Oui, bien sûr, c'est le but du refactoring, et c'est pourquoi les IDE ont des outils de refactoring pour faire ce travail rapidement et sans douleur. Même sans préfixe, changer l'usage d'une variable signifie presque certainement que son nom sera modifié. devrait à modifier.

  • "Les préfixes m'embrouillent." . Comme tout outil, jusqu'à ce que vous appreniez à l'utiliser. Une fois que votre cerveau s'est habitué aux modèles de dénomination, il filtre automatiquement les informations et la présence des préfixes ne vous dérange plus vraiment. Mais vous devez utiliser un tel schéma de façon intensive pendant une semaine ou deux avant de le rendre vraiment "fluide". Et c'est à ce moment-là que beaucoup de gens regardent leur ancien code et commencent à se demander comment ils ont pu réussir sans un bon système de préfixe.

  • "Je peux juste regarder le code pour résoudre ce problème". . Oui, mais vous n'avez pas besoin de perdre du temps à chercher ailleurs dans le code ou à vous souvenir de chaque petit détail lorsque la réponse se trouve juste à l'endroit où votre regard est déjà fixé.

  • (Certaines) de ces informations peuvent être trouvées en attendant qu'une infobulle apparaisse sur ma variable. . Oui. Lorsque cela est possible, pour certains types de préfixes, lorsque votre code se compile proprement, après une attente, vous pouvez lire une description et trouver instantanément les informations que le préfixe aurait transmises. Je pense que le préfixe est une approche plus simple, plus fiable et plus efficace.

  • "C'est plus de la dactylographie" . Vraiment ? Un caractère entier de plus ? Ou bien si - avec les outils d'autocomplétion de l'IDE, cela réduira souvent la saisie, car chaque caractère préfixe réduit considérablement l'espace de recherche. Appuyez sur "e" et les trois événements de votre classe apparaissent dans intellisense. Appuyez sur "c" et les cinq constantes sont listées.

  • "Je peux utiliser this-> au lieu de m " . Eh bien, oui, vous pouvez. Mais c'est juste un préfixe beaucoup plus laid et verbeux ! Seulement, il comporte un risque beaucoup plus grand (surtout en équipe) car pour le compilateur, c'est en option et, par conséquent, son utilisation est souvent incohérente. m d'autre part, est bref, clair, explicite et non facultatif, il est donc beaucoup plus difficile de faire des erreurs en l'utilisant.

6 votes

Je veux dire avoir lu que le problème de la notation hongroise résultait simplement d'un malentendu avec Simonyi. Il a écrit qu'un préfixe devait être utilisé pour indiquer le type d'une variable, alors qu'il voulait dire "type" comme dans "sorte de chose" et non pas type de données littéral. Plus tard, les spécialistes des plates-formes chez Microsoft l'ont repris et ont créé lpsz... et le reste appartient à l'histoire...

21 votes

"s is for static" ressemble beaucoup à la "mauvaise" forme du hongrois pour moi.

1 votes

Alors ne l'utilisez pas... A moins que vous ne pensiez que votre utilisation de la variable pourrait être influencée par le fait de savoir qu'elle est statique...

125voto

jalf Points 142628

Je n'utilise généralement pas de préfixe pour les variables membres.

J'avais l'habitude d'utiliser un m jusqu'à ce que quelqu'un fasse remarquer que "le C++ a déjà un préfixe standard pour l'accès aux membres : this-> .

C'est donc ce que j'utilise maintenant. C'est-à-dire, en cas d'ambiguïté j'ajoute le this-> mais en général, il n'y a pas d'ambiguïté, et je peux simplement faire référence directement au nom de la variable.

Pour moi, c'est le meilleur des deux mondes. J'ai un préfixe que je peux utiliser quand j'en ai besoin, et je suis libre de le laisser de côté quand c'est possible.

Bien sûr, la réponse évidente à cela est "oui, mais alors vous ne pouvez pas voir d'un coup d'œil si une variable est un membre de la classe ou non".

Ce à quoi je réponds "et alors ? Si vous avez besoin de savoir cela, votre classe a probablement trop d'état. Ou la fonction est trop grande et compliquée".

En pratique, j'ai constaté que cela fonctionne extrêmement bien. En prime, cela me permet de promouvoir une variable locale en membre de classe (ou l'inverse) facilement, sans avoir à la renommer.

Et le meilleur de tous, c'est qu'il est cohérent ! Je n'ai pas besoin de faire quoi que ce soit de spécial ou de me souvenir d'une quelconque convention pour maintenir la cohérence.


Au fait, vous ne devrait pas utilisez des caractères de soulignement pour les membres de votre classe. Vous vous rapprochez de manière inconfortable des noms qui sont réservés par l'implémentation.

La norme réserve tous les noms commençant par un double soulignement ou un soulignement suivi d'une lettre majuscule. Elle réserve également tous les noms commençant par un simple trait de soulignement. dans l'espace de nom global .

Ainsi, un membre de classe avec un trait de soulignement suivi d'une lettre minuscule est légal, mais tôt ou tard, vous ferez de même avec un identifiant commençant par une majuscule, ou vous enfreindrez l'une des règles ci-dessus.

Il est donc plus simple d'éviter les traits de soulignement en tête. Utilisez un trait de soulignement postfixe, ou un signe m_ ou simplement m si vous souhaitez encoder la portée dans le nom de la variable.

0 votes

"Donc un membre de classe avec un underscore en tête suivi d'une lettre minuscule est légal, mais tôt ou tard, vous allez faire la même chose à un identifiant commençant par une majuscule, ou autrement enfreindre l'une des règles ci-dessus." -- Les variables des membres de la classe ne sont pas dans l'espace de noms global, donc un trait de soulignement en tête est sûr, qu'il soit suivi d'une lettre minuscule ou majuscule.

3 votes

@mbarnett : Non, le trait de soulignement suivi d'une majuscule est réservé. en général et pas seulement dans l'espace de noms global.

10 votes

Surpris que le vote de cette réponse soit inférieur à celui du préfixe.

52voto

Juan Points 1235

Vous devez faire attention à l'utilisation d'un trait de soulignement en tête. Un trait de soulignement placé avant une lettre majuscule dans un mot est réservé. Par exemple :

_Foo

_L

sont tous des mots réservés tandis que

_foo

_l

ne le sont pas. Il existe d'autres situations où les caractères de soulignement précédant les lettres minuscules ne sont pas autorisés. Dans mon cas particulier, j'ai constaté que le _L était réservé par Visual C++ 2005 et que le conflit a donné des résultats inattendus.

Je ne suis pas certain de l'utilité de marquer les variables locales.

Voici un lien sur les identifiants qui sont réservés : Quelles sont les règles concernant l'utilisation d'un trait de soulignement dans un identifiant C++ ?

5 votes

En fait, _foo et _l sont tous deux réservés à l'espace de nom.

13 votes

Mais ils sont acceptés comme noms de variables membres. Je ne préfixe pas les underscores, parce que les règles sont trop confuses, et que je me suis brûlé dans le passé.

13 votes

Ce ne sont pas des mots réservés. Ce sont des noms réservés. S'ils étaient des mots réservés, vous ne pourriez pas les utiliser du tout. Parce que ce sont des noms réservés, vous pouvez les utiliser, mais à vos risques et périls.

34voto

Hooked Points 1825

Je préfère les underscores postfix, comme ça :

class Foo
{
   private:
      int bar_;

   public:
      int bar() { return bar_; }
};

1 votes

Moi aussi. Je donne aussi le même nom aux accesseurs/mutateurs.

4 votes

Intéressant. Ça a l'air un peu moche au premier abord, mais je peux voir comment ça peut être bénéfique.

6 votes

Je dirais que c'est beaucoup moins laid que : " mBar " ou " m_bar ".

23voto

Grumbel Points 1296

Dernièrement, j'ai eu tendance à préférer le préfixe m_ plutôt que de ne pas avoir de préfixe du tout, les raisons ne sont pas tant qu'il est important de marquer les variables membres, mais que cela évite toute ambiguïté, disons que vous avez du code comme :

void set_foo(int foo) { foo = foo; }

Cela ne fonctionne pas, il n'y en a qu'un. foo autorisé. Donc vos options sont :

  • this->foo = foo;

    Je ne l'aime pas, car il provoque l'ombrage des paramètres, vous ne pouvez plus utiliser g++ -Wshadow les avertissements, c'est aussi plus long à taper alors m_ . Vous pouvez également rencontrer des conflits de noms entre les variables et les fonctions lorsque vous avez un fichier de type int foo; et un int foo(); .

  • foo = foo_; o foo = arg_foo;

    Je l'utilise depuis un moment, mais cela rend les listes d'arguments hideuses, la documentation ne devrait pas avoir à s'occuper de la désambiguïsation des noms dans l'implémentation. Les conflits de noms entre variables et fonctions existent aussi ici.

  • m_foo = foo;

    La documentation de l'API reste propre, il n'y a pas d'ambiguïté entre les fonctions membres et les variables, et la saisie est plus courte. this-> . Le seul inconvénient est que cela rend les structures POD laides, mais comme les structures POD ne souffrent pas de l'ambiguïté du nom en premier lieu, on n'a pas besoin de l'utiliser avec elles. Le fait d'avoir un préfixe unique facilite également certaines opérations de recherche et de remplacement.

  • foo_ = foo;

    La plupart des avantages de m_ s'applique, mais je le rejette pour des raisons esthétiques, un trait de soulignement en fin ou en début de ligne donne simplement à la variable un aspect incomplet et déséquilibré. m_ est tout simplement mieux. Utilisation de m_ est également plus extensible, puisque vous pouvez utiliser g_ pour les globaux et s_ pour la statique.

PS : La raison pour laquelle vous ne voyez pas m_ en Python ou en Ruby, c'est parce que les deux langages utilisent leur propre préfixe. @ pour les variables membres et Python exige self. .

1 votes

Pour être juste, vous avez manqué au moins 2 autres options, par exemple (a) utiliser des noms complets comme foo uniquement pour les membres et utiliser plutôt des noms à une seule lettre ou des noms courts pour les paramètres ou d'autres éléments locaux/oubliés, tels que int f ; ou (b) préfixer le paramètres ou d'autres locaux avec quelque chose. bon point re m_ et les pods, cependant ; j'ai indépendamment atteint une préférence pour suivre ces deux directives, pour la plupart.

1 votes

@underscore_d Les noms des paramètres font partie de l'interface publique d'une classe. Cela devrait être le dernier endroit où vous ajoutez des conventions de nommage bizarres. De plus, les noms de variables en une seule lettre sont horribles et doivent être évités à tout prix. très quelques exceptions (i dans une boucle).

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