Soulignements en C++ : les tueurs silencieux de votre code
\n\nImaginez la situation : vous écrivez un code C++ propre et élégant. Le compilateur se tait, le linter ne râle pas, tout fonctionne parfaitement. Et puis — boum ! — sur un autre compilateur ou avec une nouvelle version de la norme, tout se casse sans raison apparente. Ça vous dit quelque chose ?
\n\nLe plus souvent, la racine du mal se cache dans des caractères de soulignement (_) apparemment inoffensifs. Ce sujet est un véritable "cimetière" pour les débutants et même les développeurs C++ expérimentés. Ce n'est pas pour rien que la question sur Stack Overflow concernant les règles d'utilisation des soulignements a recueilli des dizaines de milliers de vues. Mettons une bonne fois pour toutes les choses au clair : ce qui est permis, ce qui ne l'est pas, et comment ne pas tomber dans le piège du comportement indéfini (UB).
Pourquoi la norme C++ est-elle si stricte avec les soulignements ?
\n\nLa racine du problème réside dans la compatibilité et l'espace de noms global. La bibliothèque standard du C++ utilise activement les soulignements pour ses besoins internes. Si vous créez accidentellement un identifiant qui entre en conflit avec les noms internes de la bibliothèque ou du compilateur — c'est la catastrophe. Le compilateur peut silencieusement redéfinir votre variable, et vous obtiendrez un comportement indéfini (Undefined Behavior), qui se manifestera au pire moment.
\n\nLa norme C++ (depuis C++03 et dans toutes les versions ultérieures, y compris C++20 et C++23) définit clairement quels identifiants sont réservés à l'implémentation. La violation de ces règles rend votre code non seulement incorrect, mais non portable.
\n\nTop 5 des erreurs mortelles avec les soulignements
\n\nAnalysons les râteaux les plus courants sur lesquels même les programmeurs chevronnés marchent.
\n\nErreur n°1 : Double soulignement à n'importe quel endroit
\n\nRègle : Tout identifiant contenant deux caractères de soulignement consécutifs (__) à n'importe quel endroit est réservé à l'implémentation.
// MAUVAIS ! Réservé à "l'implémentation"\nint __myVariable = 42;\nvoid my__function();\nclass My__Class {};\n\n// BON\nint myVariable = 42;\nvoid my_function();\nclass MyClass {};\n\n\nPourquoi est-ce dangereux ? Le compilateur peut utiliser __myVariable pour ses propres macros internes. Par exemple, de nombreux compilateurs définissent __LINE__ ou __cplusplus. Si vous créez votre propre __LINE__, le compilateur peut simplement ignorer votre définition ou, pire encore, la remplacer par sa propre valeur. Résultat : UB.
Erreur n°2 : Soulignement + lettre majuscule dans la portée globale
\n\nRègle : Un identifiant commençant par un soulignement suivi d'une lettre majuscule (A-Z) est réservé à l'implémentation dans toute portée.
\n\n// MAUVAIS ! Même dans une portée locale !\nvoid foo() {\n int _MyVar = 10; // Violation de la règle\n}\n\n// MAUVAIS ! Globalement, encore plus\nint _GlobalVar = 20;\n\n// BON\nvoid foo() {\n int myVar = 10;\n}\n\n\nCette règle est la plus sournoise. Beaucoup pensent : "Eh bien, je suis à l'intérieur d'une fonction, qui cela dérange-t-il ?". Cela dérange ! La norme ne fait pas d'exception pour la portée locale si l'identifiant commence par un soulignement et une lettre majuscule. Le compilateur a parfaitement le droit de réserver ces noms pour ses propres besoins.
\n\nErreur n°3 : Soulignement au début + lettre minuscule dans la portée globale
\n\nRègle : Un identifiant commençant par un soulignement suivi d'une lettre minuscule (a-z) est réservé à l'implémentation dans la portée globale (namespace scope).
\n\n// MAUVAIS ! Dans l'espace de noms global\nint _myGlobal = 0;\nvoid _helperFunction();\n\n// BON — à l'intérieur d'une fonction ou d'une classe\nclass MyClass {\n int _memberVar; // À l'intérieur d'une classe — possible (mais préférez m_)\n};\n\nvoid foo() {\n int _localVar = 5; // À l'intérieur d'une fonction