Soulignements en C++ : règles non évidentes qui cassent le code

Formateur Python en ligne pour débutants

Apprenez Python facilement sans surcharge théorique. Résolvez des tâches pratiques avec vérification automatique et écrivez du code directement dans le navigateur.

Start Course

Soulignements en C++ : les tueurs silencieux de votre code

\n\n

Imaginez 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\n

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

\n\n

Pourquoi la norme C++ est-elle si stricte avec les soulignements ?

\n\n

La 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\n

La 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\n

Top 5 des erreurs mortelles avec les soulignements

\n\n

Analysons les râteaux les plus courants sur lesquels même les programmeurs chevronnés marchent.

\n\n

Erreur n°1 : Double soulignement à n'importe quel endroit

\n\n

Règle : Tout identifiant contenant deux caractères de soulignement consécutifs (__) à n'importe quel endroit est réservé à l'implémentation.

\n\n
// 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\n

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

\n\n

Erreur n°2 : Soulignement + lettre majuscule dans la portée globale

\n\n

Rè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\n

Cette 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\n

Erreur n°3 : Soulignement au début + lettre minuscule dans la portée globale

\n\n

Rè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                

Blogs

Book Recommendations