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


151

Un grand nombre de code C ++ utilise des conventions syntaxiques pour le balisage des variables membres. Les exemples courants incluent

  • m_ memberName pour les membres publics (où les membres publics sont utilisés du tout)
  • _ memberName pour les membres privés ou tous les membres

D'autres essaient de forcer en utilisant this-> member chaque fois qu'une variable membre est utilisée.

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

Dans d'autres langues, ces conventions sont beaucoup moins répandues. Je ne le vois qu'occasionnellement en code Java ou C #. Je pense que je ne l'ai jamais vu en 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-ce juste un anachronisme. D'autant plus qu'il est utilisé de manière incohérente dans les bibliothèques. Les autres langues n'ont-elles pas montré que l'on peut se passer de préfixes de membre?


15
Je le préfère; dans les bases de code complexes, il peut être important de savoir quels variables sont locaux et lesquels ne le sont pas. J'utilise généralement le préfixe pour forcer ceci -> ce que je trouve être beaucoup de frappe supplémentaire et facultatif (alors que le nom vous obligera à le faire)
Joe

5
Vous ne l'avez jamais vu dans Ruby à cause de @ for attribute et de l'idiome consistant à générer des accesseurs de préférence à l'utilisation directe des attributs.
Steve Jessop

6
Selon PEP 8, les variables membres non publiques doivent être précédées d'un trait de soulignement en Python (exemple:) self._something = 1.
Nathan Osman

2
La coloration syntaxique de l'éditeur ne devrait-elle pas être utilisée pour les identifier?
trop

2
Vous avez vu l'équivalent de this->memberdans le code Python. En Python, ce serait généralement le cas self.memberet ce n'est pas seulement une convention, cela est requis par le langage.
matec

Réponses:


49

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

_Foo

_L

sont tous des mots réservés tandis que

_foo

_l

ne sont pas. Il existe d'autres situations dans lesquelles les traits de soulignement avant les lettres minuscules ne sont pas autorisés. Dans mon cas spécifique, j'ai trouvé que le _L était réservé par Visual C ++ 2005 et que le conflit a créé des résultats inattendus.

Je ne sais pas à quel point il est utile de baliser les variables locales.

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


4
En fait, _foo et _l sont réservés à la portée de l'espace de noms.

13
Mais ils sont ok en tant que noms de variables membres. Je ne préfixe pas les traits de soulignement, car les règles sont trop confuses, et je m'étais brûlé dans le passé.
Juan

11
Ce ne sont pas des mots réservés. Ce sont des noms réservés. S'il s'agissait de mots réservés, vous ne pouviez pas du tout les utiliser. Comme ce sont des noms réservés, vous pouvez les utiliser, mais à vos risques et périls.
TonyK

236

Je suis tout à fait en faveur des préfixes bien faits .

Je pense que la notation hongroise (système) est responsable de la plupart des "bad rap" que les préfixes reçoivent.

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 long pointeur vers une chaîne terminée par nul, quand: l'architecture segmentée est une histoire ancienne, les chaînes C ++ sont par convention commune des pointeurs vers une terminaison nulle char, et ce n'est pas vraiment difficile de savoir que "customerName" est une chaîne!

Cependant, j'utilise des préfixes pour spécifier l' utilisation d'une variable (essentiellement "Apps hongrois", bien que je préfère éviter le terme hongrois car il a une association mauvaise et injuste avec le système hongrois), et c'est un gain de temps très pratique et approche de réduction des bogues .

J'utilise:

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

Là où je souhaite rendre le type clair, j'utilise des suffixes standard (par exemple, Liste, ComboBox, etc.).

Cela rend le programmeur conscient de l' utilisation de la variable chaque fois qu'il la voit / l'utilise. Le cas le plus important est sans doute "p" pour pointeur (car l'utilisation change de var. À var-> et vous devez être beaucoup plus prudent avec les pointeurs - NULL, l'arithmétique du pointeur, etc.), mais tous les autres sont très pratiques.

Par exemple, vous pouvez utiliser le même nom de variable de plusieurs manières dans une seule fonction: (ici un exemple 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:

  • Aucune confusion entre membre et paramètre
  • Aucune confusion entre index / itérateur 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 (plus courte et fonctionnent mieux avec la saisie semi-automatique) que les alternatives comme "itemIndex" et "itemPtr"

Un autre grand avantage des itérateurs "iName" est que je n'indexe jamais un tableau avec le mauvais index, et si je copie une boucle dans une autre boucle, je n'ai pas à refactoriser l'une des variables d'index de boucle.

Comparez cet exemple d'une simplicité irréaliste:

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

(qui est difficile à lire et conduit souvent à utiliser "i" là où "j" était destiné)

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 la saisie semi-automatique dans les IDE modernes, cette saisie est également rapide et facile)

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 e-mail ou un document, et quiconque lit cet extrait de code peut faire la différence entre tous les membres, constantes, pointeurs, index, etc. Je n'ai pas besoin d'ajouter "oh, et soyez prudent car "data" est un pointeur vers un pointeur ", car il s'appelle" ppData ".

Et pour la même raison, je n'ai pas besoin de sortir les yeux d'une ligne de code pour la comprendre. Je n'ai pas besoin de rechercher dans le code pour trouver si «data» est un paramètre local, un membre ou une constante. Je n'ai pas besoin de déplacer ma main vers la souris pour pouvoir placer le pointeur sur «données», puis attendre qu'une info-bulle (qui n'apparaît parfois jamais) s'affiche. Ainsi, les programmeurs peuvent lire et comprendre le code beaucoup plus rapidement, car ils ne perdent pas de temps à chercher de haut en bas ou à attendre.

(Si vous ne pensez pas perdre de temps à chercher de haut en bas pour résoudre des problèmes, trouvez du code que vous avez écrit il y a un an et que vous n'avez pas regardé depuis. Ouvrez le fichier et sautez à mi-chemin sans le lire. loin, vous pouvez lire à partir de ce point avant de ne pas savoir si quelque chose est un membre, un paramètre ou un local. Maintenant, passez à un autre emplacement aléatoire ... C'est ce que nous faisons tous toute la journée lorsque nous parcourons le code de quelqu'un d'autre ou essayant de comprendre comment appeler leur fonction)

Le préfixe 'm' évite également la notation (IMHO) laide et verbeuse "this->", 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 'data' dans la même classe, car rien n'impose une orthographe cohérente du nom).

«cette» notation est destinée à résoudre l' ambiguïté - mais pourquoi quelqu'un écrirait-il délibérément du code qui peut être ambigu? Ambiguïté va conduire à un bogue tôt ou tard. Et dans certains langages, «ceci» ne peut pas être utilisé pour les membres statiques, vous devez donc 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 est avec Intellisense et l'auto-complétion. Essayez d'utiliser Intellisense sur un formulaire Windows pour trouver un événement - vous devez faire défiler 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 répertoriés dans un groupe sous «e». Ainsi, le préfixage permet de regrouper les membres, les consts, les événements, etc. dans la liste intellisense, ce qui permet de trouver beaucoup plus rapidement et plus facilement les noms que vous voulez. (Habituellement, une méthode peut avoir environ 20 à 50 valeurs (locales, paramètres, 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 ne me propose que 2 à 5 options de saisie semi-automatique.

Je suis un programmeur paresseux, et la convention ci-dessus me fait gagner 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 schémas de préfixes sont mauvais / mauvais" . Je conviens que "m_lpsz" et ses semblables sont mal pensés et totalement inutiles. C'est pourquoi je vous conseillerais d'utiliser une notation bien conçue conçue pour prendre en charge vos besoins, plutôt que de copier quelque chose qui ne convient pas à votre contexte. (Utilisez le bon outil pour le travail).

  • "Si je change l'usage de quelque chose, je dois le renommer" . Oui, bien sûr que vous faites, c'est ce qu'est le refactoring, et pourquoi les IDE ont des outils de refactoring pour faire ce travail rapidement et sans douleur. Même sans préfixes, changer l'utilisation d'une variable signifie presque certainement que son nom doit être changé.

  • "Les préfixes me confondent" . 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 filtrera automatiquement les informations et cela ne vous dérangera pas vraiment que les préfixes soient plus là. Mais vous devez utiliser un schéma comme celui-ci solidement pendant une semaine ou deux avant de devenir vraiment "courant". Et c'est à ce moment-là que beaucoup de gens regardent l'ancien code et commencent à se demander comment ils ont pu réussir sans un bon schéma de préfixes.

  • "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 de celui-ci lorsque la réponse est juste à l'endroit où votre œil est déjà concentré.

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

  • "C'est plus typant" . Vraiment? Un personnage entier de plus? Ou est-ce - avec les outils de saisie semi-automatique IDE, cela réduira souvent la saisie, car chaque caractère de 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 répertoriées.

  • "Je peux utiliser à la this->place de m" . Eh bien, oui, vous pouvez. Mais c'est juste un préfixe beaucoup plus laid et plus bavard! Seulement, il comporte un risque beaucoup plus grand (en particulier dans les équipes) car pour le compilateur, il est facultatif , et donc son utilisation est souvent incohérente. md'autre part, il est bref, clair, explicite et non facultatif, il est donc beaucoup plus difficile de faire des erreurs en l'utilisant.


6
Je veux dire avoir lu que le problème avec Hungarien Notation vient du fait que Simonyi a été mal compris. Il a écrit qu'un préfixe devrait être utilisé pour indiquer le type d'une variable où il voulait dire "type" comme dans "genre de chose" et non un type de données littéral. Plus tard, les gars de la plate-forme chez Microsoft l'ont ramassé et ont proposé lpsz ... et le reste appartient à l'histoire ...
VoidPointer

19
"s est pour statique" ressemble à peu près à la "mauvaise" forme de hongrois pour moi.
jalf

6
@Mehrdad: Je ne pense pas que ce zsoit très souvent utile dans un langage comme C ++ où ce genre de détail d'implémentation de bas niveau devrait être encapsulé dans une classe, mais en C (où la terminaison zéro est une distinction importante) je suis d'accord avec vous. IMO, tout schéma que nous utilisons doit être adapté selon les besoins pour mieux répondre à nos propres besoins - Donc, si la terminaison zéro affecte votre utilisation de la variable, il n'y a rien de mal à déclarer "z" un préfixe utile.
Jason Williams

14
The most important case is "p" for pointer (because the usage changes from var. to var-> and you have to be much more careful with pointers.Je ne suis pas du tout d’accord. Si j'utilise un mauvais pointeur, il ne se compilera tout simplement pas ( void*peut être une exception pour les pointeurs doubles). Et l'ensemble ->plus .est suffisant pour me dire qu'il est un pointeur. De plus, si vous utilisez la saisie semi-automatique, votre éditeur a probablement des info-bulles de déclaration, ce qui élimine le besoin de préfixer les informations de variable. Quoi qu'il en soit, bonne réponse.
Thomas Eding

5
Apprécié pour les explications claires, complètes et intéressantes, cependant, il n'y a pas grand-chose ici qui suggère comment cela permet de gagner du temps en C ++ et reste largement inutilisé dans de nombreux autres langages.

115

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

Je l' habitude d'utiliser un mpréfixe, jusqu'à ce que quelqu'un a souligné que « 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 qu'en cas d'ambiguïté , j'ajoute le this->préfixe, mais généralement, aucune ambiguïté n'existe et je peux simplement me référer 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é autant que possible.

Bien sûr, le contre-sens évident à cela est "oui, mais vous ne pouvez pas voir d'un coup d'œil si une variable est un membre de classe ou non".

À quoi je dis "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 trouvé que cela fonctionne extrêmement bien. En prime, cela me permet de promouvoir facilement une variable locale auprès d'un membre de la classe (ou l'inverse), sans avoir à la renommer.

Et le meilleur de tous, c'est cohérent! Je n'ai rien à faire de spécial ni à me souvenir des conventions pour maintenir la cohérence.


En passant, vous ne devriez pas utiliser de traits de soulignement pour les membres de votre classe. Vous vous approchez inconfortablement des noms réservés par l'implémentation.

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

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

Il est donc plus facile d'éviter les principaux traits de soulignement. Utilisez un trait de soulignement postfix, ou un préfixe m_ou simplement msi vous souhaitez encoder la portée dans le nom de la variable.


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

3
@mbarnett: Non, le trait de soulignement suivi de majuscules est réservé en général , pas seulement dans l'espace de noms global.
jalf

9
surpris que le vote de cette réponse soit inférieur au préfixe.
Marson Mao

Je suis d'accord avec cette réponse, utilisez simplement this->si vous devez spécifier que c'est une variable membre, ou non, c'est bien aussi.
David Morton

De plus, vous n'avez pas à documenter votre convention pour donner votre code à d'autres personnes. Tout le monde comprend ce que cela this->signifie.
Caduchon

34

Je préfère les traits de soulignement postfix, comme tels:

class Foo
{
   private:
      int bar_;

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

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

4
Intéressant. Cela a l'air un peu moche au début, mais je peux voir comment cela peut être bénéfique.
ya23

6
Je dirais que c'est beaucoup moins moche que: "mBar" ou "m_bar".
sydan

6
mais alors vous avez vector<int> v_;et l'écriture v_.push_back(5)est assez moche aussi
avim

4
C'est le style Google C ++.
Justme0

20

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

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

Celle de la cause ne fonctionne pas, une seule est fooautorisée. Vos options sont donc:

  • this->foo = foo;

    Je n'aime pas ça, car cela provoque l'observation des paramètres, vous ne pouvez plus utiliser d' g++ -Wshadowavertissements, il est également plus long à taper ensuite m_. Vous rencontrez également toujours des conflits de dénomination entre les variables et les fonctions lorsque vous avez un int foo;et un int foo();.

  • foo = foo_; ou foo = arg_foo;

    J'utilise cela depuis un moment, mais cela rend les listes d'arguments laides, la documentation ne devrait pas avoir trait à la désambiguïté des noms dans l'implémentation. Des conflits de noms entre les variables et les fonctions existent également ici.

  • m_foo = foo;

    La documentation de l'API reste propre, vous n'obtenez pas d'ambiguïté entre les fonctions membres et les variables et son plus court à taper alors 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, il n'est pas nécessaire de l'utiliser avec elles. Avoir un préfixe unique facilite également quelques opérations de recherche et de remplacement.

  • foo_ = foo;

    La plupart des avantages de m_s'appliquent, mais je le rejette pour des raisons esthétiques, un trait de soulignement à la fin ou au début donne simplement à la variable un aspect incomplet et déséquilibré. m_semble juste mieux. L'utilisation m_est également plus extensible, comme vous pouvez l'utiliser g_pour les globaux et s_pour la statique.

PS: La raison pour laquelle vous ne voyez pas m_dans Python ou Ruby est que les deux langages appliquent leur propre préfixe, Ruby utilise @pour les variables membres et Python requiert self..


1
pour être honnête, vous avez manqué au moins 2 autres options, par exemple (a) utiliser des noms complets comme foouniquement pour les membres et utiliser à la place des noms simples ou courts pour les paramètres ou d'autres sections locales / jetables, comme int f; ou (b) préfixez les paramètres ou d'autres sections locales avec quelque chose. bon point re m_et pods, cependant; Je suis indépendamment arrivé à une préférence pour suivre ces deux directives, pour la plupart.
underscore_d

12

Lors de la lecture d'une fonction membre, savoir à qui «appartient» chaque variable est absolument essentiel pour comprendre la signification de la variable. Dans une fonction comme celle-ci:

void Foo::bar( int apples )
{
    int bananas = apples + grapes;
    melons = grapes * bananas;
    spuds += melons;
}

... il est assez facile de voir d'où viennent les pommes et les bananes, mais qu'en est-il des raisins, des melons et des patates? Devrions-nous regarder dans l'espace de noms global? Dans la déclaration de classe? La variable est-elle membre de cet objet ou membre de la classe de cet objet? Sans connaître la réponse à ces questions, vous ne pouvez pas comprendre le code. Et dans une fonction plus longue, même les déclarations de variables locales comme les pommes et les bananes peuvent se perdre dans le shuffle.

La préparation d'une étiquette cohérente pour les globaux, les variables membres et les variables membres statiques (peut-être g_, m_ et s_ respectivement) clarifie instantanément la situation.

void Foo::bar( int apples )
{
    int bananas = apples + g_grapes;
    m_melons = g_grapes * bananas;
    s_spuds += m_melons;
}

Il faudra peut-être un certain temps pour s'y habituer au début - mais ensuite, qu'est-ce qui ne fonctionne pas en programmation? Il y a eu un jour où même {and} vous avait l'air bizarre. Et une fois que vous vous y êtes habitué, ils vous aident à comprendre le code beaucoup plus rapidement.

(Utiliser "this->" à la place de m_ a du sens, mais est encore plus long et visuellement perturbateur. Je ne le vois pas comme une bonne alternative pour baliser toutes les utilisations des variables membres.)

Une objection possible à l'argument ci-dessus serait d'étendre l'argument aux types. Il peut également être vrai que la connaissance du type d'une variable «est absolument essentielle pour comprendre la signification de la variable». Si tel est le cas, pourquoi ne pas ajouter un préfixe à chaque nom de variable qui identifie son type? Avec cette logique, vous vous retrouvez avec la notation hongroise. Mais beaucoup de gens trouvent la notation hongroise laborieuse, laide et inutile.

void Foo::bar( int iApples )
{
    int iBananas = iApples + g_fGrapes;
    m_fMelons = g_fGrapes * iBananas;
    s_dSpuds += m_fMelons;
}

hongrois faitdites-nous quelque chose de nouveau sur le code. Nous comprenons maintenant qu'il existe plusieurs transtypages implicites dans la fonction Foo :: bar (). Le problème avec le code maintenant est que la valeur des informations ajoutées par les préfixes hongrois est faible par rapport au coût visuel. Le système de type C ++ comprend de nombreuses fonctionnalités pour aider les types à bien fonctionner ensemble ou pour déclencher un avertissement ou une erreur du compilateur. Le compilateur nous aide à gérer les types - nous n'avons pas besoin de notation pour le faire. Nous pouvons déduire assez facilement que les variables de Foo :: bar () sont probablement numériques, et si c'est tout ce que nous savons, c'est assez bien pour avoir une compréhension générale de la fonction. Par conséquent, l'intérêt de connaître le type précis de chaque variable est relativement faible. Pourtant, la laideur d'une variable comme "s_dSpuds" (ou même simplement "dSpuds") est excellente. Alors,


Merci pour l'idée s_. Cela me semble très utile et ne m’était jamais venu à l’esprit.
Chris Olsen

10

Je ne peux pas dire à quel point c'est large, mais personnellement, j'ai toujours (et j'ai toujours) préfixé mes variables membres avec «m». Par exemple:

class Person {
   .... 
   private:
       std::string mName;
};

C'est la seule forme de préfixe que j'utilise (je suis très contre la notation hongroise) mais cela m'a bien aidé au fil des ans. En passant, je déteste généralement l'utilisation de traits de soulignement dans les noms (ou n'importe où ailleurs d'ailleurs), mais fais une exception pour les noms de macro de préprocesseur, car ils sont généralement tous en majuscules.


5
Le problème avec l'utilisation de m, plutôt que m_ (ou _) est avec la mode actuelle pour le cas des chameaux, il est difficile de lire certains noms de variables.
Martin Beckett

1
@Neil je suis avec vous. @mgb: Je déteste les noms commençant par '_' C'est juste une invitation à ce que les choses tournent mal à l'avenir.
Martin York

1
@Neil: Quelle convention utilisez-vous alors, si vous n'utilisez pas de traits de soulignement et n'utilisez pas camelcase?
jalf

2
Je crois comprendre que c'est camelCase qui rend l'utilisation de juste m pour des variables comme «apData» déroutante - cela devient «mapData» plutôt que «m_apData». J'utilise _camelCase pour les variables de membre protégées / privées car il se démarque
Martin Beckett

10
@MartinBeckett: Vous devriez mettre aen majuscule dans ce scénario - vous ne le faites pas correctement autrement. mApData( mpréfixe, alors le nom de la variable est apData).
Platinum Azure

8

La raison principale d'un préfixe de membre est de faire la distinction entre une fonction membre locale et une variable membre avec le même nom. Ceci est utile si vous utilisez des getters avec le nom de l'objet.

Considérer:

class person
{
public:
    person(const std::string& full_name)
        : full_name_(full_name)
    {}

    const std::string& full_name() const { return full_name_; }
private:
    std::string full_name_;
};

La variable membre n'a pas pu être appelée full_name dans ce cas. Vous devez renommer la fonction membre en get_full_name () ou décorer la variable membre d'une manière ou d'une autre.


1
C'est la raison pour laquelle je préfixe. Je pense que foo.name()c'est beaucoup plus lisible qu'à foo.get_name()mon avis.
Terrabits le

6

Je ne pense pas qu'une syntaxe ait une valeur réelle par rapport à une autre. Tout se résume, comme vous l'avez mentionné, à l'uniformité entre les fichiers source.

Le seul point où je trouve ces règles intéressantes, c'est quand j'ai besoin de 2 choses nommées identiques, par exemple:

void myFunc(int index){
  this->index = index;
}

void myFunc(int index){
  m_index = index;
}

Je l'utilise pour différencier les deux. De plus, lorsque j'emballe des appels, comme à partir de Windows Dll, RecvPacket (...) de la Dll peut être enveloppé dans RecvPacket (...) dans mon code. Dans ces cas particuliers, l'utilisation d'un préfixe tel que "_" peut faire en sorte que les deux se ressemblent, il est facile d'identifier lequel est lequel, mais différent pour le compilateur


6

Certaines réponses se concentrent sur la refactorisation, plutôt que sur les conventions de dénomination, comme moyen d'améliorer la lisibilité. Je ne pense pas que l'un puisse remplacer l'autre.

J'ai connu des programmeurs qui ne sont pas à l'aise avec les déclarations locales; ils préfèrent placer toutes les déclarations en haut d'un bloc (comme en C), afin de savoir où les trouver. J'ai constaté que, là où la portée le permet, déclarer les variables là où elles sont utilisées pour la première fois diminue le temps que je passe à regarder en arrière pour trouver les déclarations. (Cela est vrai pour moi même pour les petites fonctions.) Cela me permet de mieux comprendre le code que je regarde.

J'espère que le lien avec les conventions de dénomination des membres est suffisamment clair: lorsque les membres ont un préfixe uniforme, je n'ai jamais à regarder en arrière du tout; Je sais que la déclaration ne sera même pas trouvée dans le fichier source.

Je suis sûr que je n'ai pas commencé par préférer ces styles. Pourtant, au fil du temps, travaillant dans des environnements où ils étaient utilisés de manière cohérente, j'ai optimisé ma réflexion pour en tirer parti. Je pense qu'il est possible que de nombreuses personnes qui se sentent actuellement mal à l'aise avec eux en viennent également à les préférer, étant donné leur utilisation constante.


5

Ces conventions ne sont que cela. La plupart des magasins utilisent des conventions de code pour faciliter la lisibilité du code afin que tout le monde puisse facilement consulter un morceau de code et déchiffrer rapidement entre des éléments tels que des membres publics et privés.


«entre des éléments tels que les membres publics et privés» - dans quelle mesure est-ce vraiment courant? Je ne me souviens pas l'avoir vu, mais encore une fois, je ne passe pas en revue les bases de code ou quoi que ce soit.
underscore_d

Je ne le fais pas dans mon propre codage, mais j'ai travaillé à des endroits où nous devions le faire en nous basant sur leurs guides de conventions de code. Je préfère ne pas le faire car presque tous les IDE afficheront les variables privées d'une couleur différente.
M. Will

Hmm, je suppose que cela n'arrive que dans des situations différentes de la mienne. Normalement, j'utilise soit classtous les membres dont les membres sont private/ protected, soit les POD structdont toutes les variables sont public(et souvent aussi const). Donc, je n'ai jamais besoin de m'interroger sur le niveau d'accès d'un membre donné.
underscore_d

5

D'autres essaient de forcer en utilisant this-> member chaque fois qu'une variable membre est utilisée

C'est généralement parce qu'il n'y a pas de préfixe . Le compilateur a besoin de suffisamment d'informations pour résoudre la variable en question, que ce soit un nom unique à cause du préfixe ou via le thismot - clé.

Donc, oui, je pense que les préfixes sont toujours utiles. Pour ma part, je préférerais taper «_» pour accéder à un membre plutôt que «this->».


3
le compilateur peut le résoudre de toute façon ... les variables locales masqueront celles qui ont une portée supérieure dans la plupart des langages. C'est pour le bénéfice (douteux) des humains lisant le code. Tout IDE décent mettra en évidence les habitants / membres / globaux de différentes manières, donc il n'y a pas besoin de ce genre de choses
rmeador

1
Exactement. Les sections locales cacheront les membres de la classe. Considérez un constructeur qui définit ces membres. Il est généralement judicieux de nommer les paramètres de la même manière que les membres.
Kent Boogaart

6
Pourquoi est-ce une odeur de code? Je dirais que c'est parfaitement courant et raisonnable, surtout en ce qui concerne les constructeurs.
Kent Boogaart

3
Un constructeur doit (généralement) définir des locals dans sa liste d'initialisation. Et là, les paramètres ne masquent pas les noms de champs, mais les deux sont accessibles - vous pouvez donc écrirestruct Foo { int x; Foo(int x) : x(x) { ... } };
Pavel Minaev

2
Je suppose que le problème avec cela survient lorsque vous Foo(int x, bool blee) : x(x) { if (blee) x += bleecount; } // oops, forgot this->préférez appeler mes variables membres quelque chose d'utile, puis donner des paramètres de constructeur qui leur correspondent des noms abrégés:Foo(int f) : foo(f) {...}
Steve Jessop

4

D'autres langages utiliseront des conventions de codage, ils tendent simplement à être différents. C #, par exemple, a probablement deux styles différents que les gens ont tendance à utiliser, soit l'une des méthodes C ++ (_variable, mVariable ou un autre préfixe tel que la notation hongroise), ou ce que j'appelle la méthode StyleCop.

private int privateMember;
public int PublicMember;

public int Function(int parameter)
{
  // StyleCop enforces using this. for class members.
  this.privateMember = parameter;
}

En fin de compte, cela devient ce que les gens savent et ce qui semble le mieux. Personnellement, je pense que le code est plus lisible sans la notation hongroise, mais il peut devenir plus facile de trouver une variable avec intellisense par exemple si la notation hongroise est jointe.

Dans mon exemple ci-dessus, vous n'avez pas besoin d'un préfixe m pour les variables membres, car vous préfixez votre utilisation avec ceci. indique la même chose dans une méthode appliquée par le compilateur.

Cela ne signifie pas nécessairement que les autres méthodes sont mauvaises, les gens s'en tiennent à ce qui fonctionne.


3

Lorsque vous avez une grande méthode ou des blocs de code, il est pratique de savoir immédiatement si vous utilisez une variable locale ou un membre. c'est pour éviter les erreurs et pour une meilleure clarté!


3
Si vous avez une grande méthode, pour une meilleure clarté, décomposez-la.
sbi

4
Il y a de nombreuses raisons de ne pas décomposer certaines grandes méthodes. Par exemple, si votre méthode doit conserver beaucoup d'état local, vous devez soit transmettre de nombreux paramètres à vos méthodes subordonnées, créer de nouvelles classes qui existent uniquement dans le but de passer des données entre ces méthodes, ou déclarer les données d'état comme données de membre de la classe parente. Tous ces éléments présentent des problèmes qui affecteraient la clarté ou la maintenabilité de la méthode, par rapport à une seule méthode longue (en particulier une méthode dont la logique est simple).
Steve Broberg

3
@sbi: Les lignes directrices ne sont que cela; des directives, pas des règles. Parfois, vous avez besoin de méthodes volumineuses qui ne se prêtent pas logiquement à être séparées, et parfois les noms de paramètres entrent en conflit avec les membres.
Ed S.

Veuillez ne pas rendre vos variables membres publiques. Utilisez simplement des accesseurs. Les parenthèses doivent indiquer au lecteur qu'il s'agit d'une variable membre.
jkeys

Notez qu'il y a un avertissement dans gcc (> = 4.6) pour détecter les -Wshadow
conflits

3

OMI, c'est personnel. Je ne mets aucun préfixe. Quoi qu'il en soit, si le code est censé être public, je pense qu'il devrait mieux avoir des préfixes, afin qu'il soit plus lisible.

Souvent, les grandes entreprises utilisent leurs propres «règles de développement».
Btw, le plus drôle mais le plus intelligent que j'ai vu était DRY KISS (Ne vous répétez pas. Keep It Simple, Stupid). :-)


3

Comme d'autres l'ont déjà dit, il est important d'être familier (adapter les styles et les conventions de dénomination à la base de code dans laquelle vous écrivez) et d'être cohérent.

Pendant des années, j'ai travaillé sur une grande base de code qui utilise à la fois la convention "this->" et un suffixe notation de soulignement pour les variables membres. Au fil des années, j'ai également travaillé sur des projets plus petits, dont certains n'avaient aucune sorte de convention pour nommer les variables membres, et d'autres qui avaient des conventions différentes pour nommer les variables membres. De ces petits projets, j'ai toujours trouvé que ceux qui n'avaient aucune convention étaient les plus difficiles à aborder rapidement et à comprendre.

Je suis très réticent à la dénomination. Je m'inquiéterai du nom à attribuer à une classe ou à une variable au point que, si je ne parviens pas à trouver quelque chose que je trouve «bon», je choisirai de le nommer quelque chose de non-sens et de fournir un commentaire décrivant ce que c'est vraiment est. De cette façon, au moins le nom signifie exactement ce que je veux dire - ni plus ni moins. Et souvent, après l'avoir utilisé pendant un petit moment, je découvre ce que le nom devrait vraiment être et je peux revenir en arrière et modifier ou refactoriser de manière appropriée.

Un dernier point sur le sujet d'un IDE faisant le travail - c'est bien beau, mais les IDE ne sont souvent pas disponibles dans les environnements où j'ai effectué le travail le plus urgent. Parfois, la seule chose disponible à ce stade est une copie de «vi». En outre, j'ai vu de nombreux cas où l'achèvement du code IDE a propagé la stupidité telle que l'orthographe incorrecte des noms. Ainsi, je préfère ne pas avoir à compter sur une béquille IDE.


3

L'idée originale des préfixes sur les variables membres C ++ était de stocker des informations de type supplémentaires que le compilateur ne connaissait pas. Ainsi, par exemple, vous pourriez avoir une chaîne qui a une longueur fixe de caractères, et une autre qui est variable et terminée par un '\ 0'. Pour le compilateur, ils sont tous les deux char *, mais si vous essayez de copier de l'un à l'autre, vous avez d'énormes problèmes. Alors, du haut de ma tête,

char *aszFred = "Hi I'm a null-terminated string";
char *arrWilma = {'O', 'o', 'p', 's'};

où "asz" signifie que cette variable est "chaîne ascii (terminée par un zéro) et" arr "signifie que cette variable est un tableau de caractères.

Ensuite, la magie opère. Le compilateur sera parfaitement satisfait de cette déclaration:

strcpy(arrWilma, aszFred);

Mais vous, en tant qu'humain, pouvez le regarder et dire "hé, ces variables ne sont pas vraiment du même type, je ne peux pas faire ça".

Malheureusement, de nombreux endroits utilisent des standards tels que "m_" pour les variables membres, "i" pour les entiers peu importe comment ils sont utilisés, "cp" pour les pointeurs char. En d'autres termes, ils dupliquent ce que le compilateur sait et rendent le code difficile à lire en même temps. Je crois que cette pratique pernicieuse devrait être interdite par la loi et passible de sanctions sévères.

Enfin, il y a deux points que je devrais mentionner:

  • Une utilisation judicieuse des fonctionnalités C ++ permet au compilateur de connaître les informations que vous deviez encoder dans des variables brutes de style C. Vous pouvez créer des classes qui n'autoriseront que des opérations valides. Cela devrait être fait autant que possible.
  • Si vos blocs de code sont si longs que vous oubliez de quel type est une variable avant de l'utiliser, ils sont bien trop longs. N'utilisez pas de noms, réorganisez.

Les préfixes qui indiquent le type ou le type de variable valent également la peine d'être discutés, mais je faisais principalement référence aux préfixes indiquant si quelque chose est un membre / champ (privé). La notation hongroise inversée que vous mentionnez peut être très pratique lorsqu'elle est appliquée intelligemment (comme dans votre exemple). Mon exemple préféré où cela a du sens est celui des coordonnées relatives et absolues. lorsque vous voyez absX = relX, vous pouvez clairement voir que quelque chose ne va pas. Vous pouvez également nommer les fonctions en conséquence: absX = absFromRel (relX, offset);
VoidPointer

Remarque: l'initialisation de aszFred est discutable (offrant un accès non-const à une chaîne littérale), et l'initialisation de arrWilma ne se compilera même pas. (Vous aviez probablement l'intention de déclarer arrWilma comme un tableau, au lieu d'un pointeur!) Pas de problème cependant, car vous avez écrit que c'est juste au-dessus de votre tête ... :-)
Niels Dekker

Oups, vous avez absolument raison. Les enfants, n'essayez pas ça à la maison. Faites ceci: 'const char * aszFred = "Salut, je suis une chaîne terminée par un nul"; char arrWilma [] = {'O', 'o', 'p', 's'}; '
AL Flanagan

3

Notre projet a toujours utilisé «son» comme préfixe pour les données des membres et «le» comme préfixe pour les paramètres, sans préfixe pour les locaux. C'est un peu moche, mais il a été adopté par les premiers développeurs de notre système car ils l'ont vu utilisé comme convention par certaines bibliothèques de sources commerciales que nous utilisions à l'époque (XVT ou RogueWave - peut-être les deux). Vous obtiendrez donc quelque chose comme ça:

void
MyClass::SetName(const RWCString &theName)
{
   itsName = theName;
}

La principale raison pour laquelle je vois les préfixes de portée (et pas d'autres - je déteste la notation hongroise) est que cela vous empêche d'avoir des problèmes en écrivant du code où vous pensez que vous faites référence à une variable, mais vous faites vraiment référence à une autre variable avec le même nom défini dans la portée locale. Cela évite également le problème de trouver un nom de variable pour représenter ce même concept, mais avec des portées différentes, comme dans l'exemple ci-dessus. Dans ce cas, vous devrez de toute façon trouver un préfixe ou un nom différent pour le paramètre "theName" - pourquoi ne pas créer une règle cohérente qui s'applique partout.

Le simple fait d'utiliser this-> n'est pas vraiment suffisant - nous ne sommes pas aussi intéressés par la réduction de l'ambiguïté que par la réduction des erreurs de codage, et masquer les noms avec des identifiants à portée locale peut être pénible. Certes, certains compilateurs peuvent avoir la possibilité de déclencher des avertissements pour les cas où vous avez masqué le nom dans une portée plus large, mais ces avertissements peuvent devenir une nuisance si vous travaillez avec un grand nombre de bibliothèques tierces qui ont choisi les noms de variables inutilisées qui entrent parfois en conflit avec les vôtres.

En ce qui concerne le son / le lui-même - honnêtement, je trouve qu'il est plus facile à taper que les traits de soulignement (en tant que dactylo tactile, j'évite autant que possible les traits de soulignement - trop d'étirement des lignes d'origine), et je le trouve plus lisible qu'un mystérieux soulignement.


C'est la solution la plus intuitive avec la courbe d'apprentissage la plus rapide que j'ai jamais entendue. Je souhaite que les langages parlés soient plus flexibles pour gérer tous ceux-ci afin que nous n'ayons pas à penser à proposer de nouvelles techniques pour résoudre les ambiguïtés dans le code.
Guney Ozsan

2

Je l'utilise parce que Intellisense de VC ++ ne peut pas dire quand afficher les membres privés lors de l'accès hors de la classe. La seule indication est un petit symbole de «verrouillage» sur l'icône de champ dans la liste Intellisense. Cela facilite simplement l'identification des membres privés (champs). Aussi une habitude de C # pour être honnête.

class Person {
   std::string m_Name;
public:
   std::string Name() { return m_Name; }
   void SetName(std::string name) { m_Name = name; }
};

int main() {
  Person *p = new Person();
  p->Name(); // valid
  p->m_Name; // invalid, compiler throws error. but intellisense doesn't know this..
  return 1;
}

2

Je pense que si vous avez besoin de préfixes pour distinguer les membres de la classe des paramètres de fonction membre et des variables locales, soit la fonction est trop grande, soit les variables sont mal nommées. S'il ne rentre pas sur l'écran pour que vous puissiez facilement voir ce qui est quoi, refactoriser.

Étant donné qu'ils sont souvent déclarés loin de l'endroit où ils sont utilisés, je trouve que les conventions de dénomination des constantes globales (et des variables globales, bien que l'OMI ait rarement besoin de les utiliser) ont du sens. Mais sinon, je ne vois pas grand besoin.

Cela dit, j'avais l'habitude de mettre un trait de soulignement à la fin de tous les membres de la classe privée. Étant donné que toutes mes données sont privées, cela implique que les membres ont un trait de soulignement à la fin. Je ne fais généralement plus cela dans de nouvelles bases de code, mais comme, en tant que programmeur, vous travaillez principalement avec de l'ancien code, je le fais toujours beaucoup. Je ne sais pas si ma tolérance pour cette habitude vient du fait que je le faisais toujours et que je le fais toujours régulièrement ou si cela a vraiment plus de sens que le marquage des variables membres.


2
Cela reflète beaucoup mon sentiment sur cette question. Le code doit être lisible sans recourir à des préfixes. Peut-être que nous ne voyons pas autant d'utilisations de préfixes dans des langages plus modernes parce que leurs communautés d'utilisateurs ont adopté la lisibilité un peu plus que ce que vous voyez parfois en C ++. Bien entendu, C ++ peut et doit être lisible. C'est juste que beaucoup de C ++ illisible ont été écrits au fil des ans.
VoidPointer


1

Il est utile de faire la différence entre les variables membres et les variables locales en raison de la gestion de la mémoire. De manière générale, les variables membres allouées au tas doivent être détruites dans le destructeur, tandis que les variables locales allouées au tas doivent être détruites dans cette portée. L'application d'une convention de dénomination aux variables membres facilite la gestion correcte de la mémoire.


comment? Le destructeur n'a pas accès aux variables locales déclarées dans d'autres fonctions, il n'y a donc pas de place pour la confusion. De plus, les variables locales allouées au tas ne devraient pas exister . Et les variables membres allouées au tas ne devraient exister qu'à l'intérieur des classes RAII, à peu près.
jalf

"Les variables locales allouées au tas ne devraient pas exister" est un peu fort. Mais si / quand vous les utilisez, il est très important de s'assurer qu'ils sont désalloués correctement, donc une convention de dénomination disciplinée pour les variables membres par rapport aux variables locales aide énormément à garantir cela.
frankster

1

Code Complete recommande m_varname pour les variables membres.

Bien que je n'ai jamais pensé que la notation m_ était utile, je donnerais du poids à l'opinion de McConnell dans la construction d'une norme.


2
Sauf s'il explique pourquoi le trait de soulignement. Je suis un grand fan de son livre "Rapid Development", que j'ai recommandé ici de nombreuses fois, mais encore moins de "Code Complete" (que j'avoue ne pas avoir lu depuis sa sortie).

1

Je n'utilise presque jamais de préfixes devant mes noms de variables. Si vous utilisez un IDE suffisamment décent, vous devriez pouvoir refactoriser et trouver facilement des références. J'utilise des noms très clairs et je n'ai pas peur d'avoir de longs noms de variables. Je n'ai jamais eu de problème avec la portée non plus avec cette philosophie.

La seule fois où j'utilise un préfixe serait sur la ligne de signature. Je préfixe les paramètres à une méthode avec _ afin que je puisse programmer de manière défensive autour d'eux.


1

Vous ne devriez jamais avoir besoin d'un tel préfixe. Si un tel préfixe vous offre un avantage, votre style de codage en général doit être corrigé, et ce n'est pas le préfixe qui empêche votre code d'être clair. Les mauvais noms de variables typiques incluent "autre" ou "2". Vous ne corrigez pas cela en exigeant qu'il soit mOther, vous le corrigez en amenant le développeur à réfléchir à ce que cette variable fait là dans le contexte de cette fonction. Peut-être qu'il voulait dire remoteSide, ou newValue, ou secondTestListener ou quelque chose dans cette portée.

C'est un anachronisme efficace qui se propage encore trop loin. Arrêtez de préfixer vos variables et donnez-leur des noms propres dont la clarté reflète la durée de leur utilisation. Jusqu'à 5 lignes, vous pouvez l'appeler "i" sans confusion; au-delà de 50 lignes, vous avez besoin d'un nom assez long.


1

J'aime que les noms de variables ne donnent qu'une signification aux valeurs qu'ils contiennent et ne tiennent pas compte de la manière dont ils sont déclarés / implémentés. Je veux savoir ce que signifie la valeur, point final. Peut-être ai-je fait plus qu'une quantité moyenne de refactorisation, mais je trouve que l'intégration de la façon dont quelque chose est implémenté dans le nom rend la refactorisation plus fastidieuse que nécessaire. Les préfixes indiquant où et comment les membres d'objet sont déclarés sont spécifiques à l'implémentation.

color = Red;

La plupart du temps, je ne me soucie pas de savoir si Red est une énumération, une structure ou autre, et si la fonction est si grande que je ne me souviens pas si la couleur a été déclarée localement ou est un membre, il est probablement temps de casser la fonction en unités logiques plus petites.

Si votre complexité cyclomatique est si grande que vous ne pouvez pas suivre ce qui se passe dans le code sans des indices spécifiques à l'implémentation intégrés dans les noms des choses, vous devrez probablement réduire la complexité de votre fonction / méthode.

La plupart du temps, je n'utilise «this» que dans les constructeurs et les initialiseurs.


0

J'utilise m_ pour les variables membres juste pour profiter d'Intellisense et des fonctionnalités IDE associées. Lorsque je codifie l'implémentation d'une classe, je peux taper m_ et voir la liste déroulante avec tous les membres m_ regroupés.

Mais je pourrais vivre sans m_ sans problème, bien sûr. C'est juste mon style de travail.


Vous pouvez également taperthis->
Toast

0

Selon les NORMES DE CODAGE C ++ DES VÉHICULES AÉRIENS JOINT STRIKE FIGHTER (décembre 2005):

Règle 67 de l'AV

Les données publiques et protégées ne doivent être utilisées que dans des structures et non dans des classes. Justification: Une classe est capable de maintenir son invariant en contrôlant l'accès à ses données. Cependant, une classe ne peut pas contrôler l'accès à ses membres si ces membres ne sont pas privés. Par conséquent, toutes les données d'une classe doivent être privées.

Ainsi, le préfixe "m" devient inutile car toutes les données doivent être privées.

Mais c'est une bonne habitude d'utiliser le préfixe p avant un pointeur car c'est une variable dangereuse.


0

Beaucoup de ces conventions datent d'une époque sans éditeurs sophistiqués. Je recommanderais d'utiliser un IDE approprié qui vous permet de colorer tous les types de variables. La couleur est de loin plus facile à repérer que n'importe quel préfixe.

Si vous avez besoin d'obtenir encore plus de détails sur une variable, tout IDE moderne devrait être capable de vous la montrer en déplaçant le curseur ou le curseur dessus. Et si vous utilisez une variable d'une manière incorrecte (par exemple un pointeur avec l'opérateur.), Vous obtiendrez une erreur, de toute façon.

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.