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

13voto

OldPeculier Points 1972

Lors de la lecture d'une fonction membre, il est absolument essentiel de savoir qui "possède" chaque variable pour en comprendre la signification. 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 du raisin, des melons et des patates ? Devons-nous regarder dans l'espace de noms global ? Dans la déclaration de la classe ? La variable est-elle un membre de cet objet ou un 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 la confusion.

L'ajout d'une étiquette cohérente pour les globales, 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 s'y habituer au début, mais qu'est-ce qui ne l'est pas en programmation ? Il fut un jour où même { et } vous semblaient étranges. 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 perturbant. Je ne le vois pas comme une bonne alternative pour marquer toutes les utilisations des variables membres).

Une objection possible à l'argument ci-dessus serait d'étendre l'argument aux types. Il pourrait aussi être vrai que connaître le type d'une variable "est absolument essentiel pour comprendre la signification de la variable". Si c'est le cas, pourquoi ne pas ajouter un préfixe à chaque nom de variable qui identifie son type ? Avec cette logique, on aboutit à la notation hongroise. Mais beaucoup de gens trouvent la notation hongroise laborieuse, laide et peu utile.

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

Hongrois fait nous apprennent quelque chose de nouveau sur le code. Nous comprenons maintenant qu'il y a plusieurs casts implicites dans la fonction Foo::bar(). Le problème avec le code maintenant est que la valeur de l'information ajoutée par les préfixes hongrois est faible par rapport au coût visuel. Le système de types C++ comprend de nombreuses fonctionnalités qui permettent aux types de bien fonctionner ensemble ou de déclencher un avertissement ou une erreur du compilateur. Le compilateur nous aide à traiter 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 suffisant 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 grande. Ainsi, une analyse coût-bénéfice rejette la notation hongroise, alors que l'avantage de g_, s_ et m_ dépasse le coût aux yeux de nombreux programmeurs.

0 votes

Merci pour cette idée de s_. Elle semble très utile, et d'une certaine manière, je n'y avais jamais pensé.

10voto

Je ne peux pas dire à quel point c'est répandu, mais personnellement, j'ai toujours (et j'ai toujours) préfixé mes variables de membre 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 opposé à la notation hongroise) mais elle m'a bien servi au fil des ans. Par ailleurs, je déteste généralement l'utilisation de caractères de soulignement dans les noms (ou ailleurs d'ailleurs), mais je fais une exception pour les noms de macros de préprocesseur, car ils sont généralement tout en majuscules.

5 votes

Le problème de l'utilisation de m, plutôt que de m_ (ou _) est qu'avec la mode actuelle des majuscules, il est difficile de lire certains noms de variables.

0 votes

J'ai un plan astucieux pour faire face à ce problème : je n'utilise pas d'étui à chameau.

1 votes

@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 dans le futur.

9voto

La raison principale pour un préfixe de membre est de distinguer 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 la chose.

Pensez-y :

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 ne pouvait pas ê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.

2 votes

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

6voto

Dan Breslau Points 9217

Certaines réponses mettent l'accent sur le remaniement, 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.

Je connais des programmeurs qui ne sont pas à l'aise avec l'utilisation de 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, lorsque le scoping le permet, déclarer les variables à l'endroit 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. (C'est vrai pour moi, même pour les petites fonctions.) Cela me permet de comprendre plus facilement 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 sont uniformément préfixés, je n'ai jamais besoin de regarder en arrière ; 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. Mais au fil du temps, en travaillant dans des environnements où ils étaient utilisés de manière constante, j'ai optimisé ma pensée pour en tirer parti. Je pense qu'il est possible que de nombreuses personnes qui se sentent actuellement mal à l'aise avec ces styles finissent par les préférer, à condition de les utiliser régulièrement.

6voto

Eric Points 8793

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

Le seul cas où je trouve de telles règles intéressantes est lorsque j'ai besoin de deux choses nommées de manière identique, par exemple :

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

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

Je l'utilise pour différencier les deux. Également lorsque j'enveloppe les appels, comme ceux de la Dll de Windows, RecvPacket(...) de la Dll peut être enveloppée dans RecvPacket(...) dans mon code. Dans ces cas particuliers, l'utilisation d'un préfixe comme "_" peut rendre les deux éléments similaires, faciles à identifier, mais différents pour le compilateur.

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