Sublinhados em C++: assassinos silenciosos do seu código
\n\nImagine a situação: você está escrevendo um código C++ limpo e bonito. O compilador fica em silêncio, o linter não reclama, tudo funciona perfeitamente. E então — bum! — e em algum outro compilador ou com uma nova versão do padrão, tudo quebra sem motivo aparente. Familiar?
\n\nNa maioria das vezes, a raiz do mal está nos caracteres de sublinhado (_) aparentemente inofensivos. Este tópico é um verdadeiro "cemitério" para iniciantes e até mesmo para desenvolvedores C++ experientes. Não é à toa que a pergunta no Stack Overflow sobre as regras de uso de sublinhados acumulou dezenas de milhares de visualizações. Vamos entender de uma vez por todas o que pode, o que não pode e como não cair na armadilha do comportamento indefinido (UB).
Por que o padrão C++ é tão rigoroso com sublinhados?
\n\nA raiz do problema está na compatibilidade e no espaço de nomes global. A biblioteca padrão do C++ usa ativamente sublinhados para suas necessidades internas. Se você acidentalmente criar um identificador que entre em conflito com nomes internos da biblioteca ou do compilador — pronto, é o fim. O compilador pode redefinir silenciosamente sua variável, e você terá um comportamento indefinido (Undefined Behavior), que se manifestará no momento mais inoportuno.
\n\nO padrão C++ (a partir do C++03 e em todas as versões subsequentes, incluindo C++20 e C++23) especifica claramente quais identificadores são reservados para a implementação. Violar essas regras torna seu código não apenas incorreto, mas não portável.
\n\nTop 5 erros fatais com sublinhados
\n\nVamos analisar os erros mais comuns em que até programadores experientes pisam.
\n\nErro nº1: Sublinhado duplo em qualquer lugar
\n\nRegra: Qualquer identificador que contenha dois caracteres de sublinhado consecutivos (__) em qualquer lugar é reservado para a implementação.
// RUIM! Reservado para a "implementação"\nint __myVariable = 42;\nvoid my__function();\nclass My__Class {};\n\n// BOM\nint myVariable = 42;\nvoid my_function();\nclass MyClass {};\n\n\nPor que isso é perigoso? O compilador pode usar __myVariable para seus macros internos. Por exemplo, muitos compiladores definem __LINE__ ou __cplusplus. Se você criar seu próprio __LINE__, o compilador pode simplesmente ignorar sua definição ou, pior ainda, substituí-la pelo seu próprio valor. O resultado é UB.
Erro nº2: Sublinhado + letra maiúscula no escopo global
\n\nRegra: Um identificador que começa com sublinhado, seguido por uma letra maiúscula (A-Z), é reservado para a implementação em qualquer escopo.
\n\n// RUIM! Mesmo em escopo local!\nvoid foo() {\n int _MyVar = 10; // Violação da regra\n}\n\n// RUIM! Globalmente — ainda mais\nint _GlobalVar = 20;\n\n// BOM\nvoid foo() {\n int myVar = 10;\n}\n\n\nEsta regra é a mais traiçoeira. Muitos pensam: "Bem, estou dentro de uma função, quem isso incomoda?". Incomoda! O padrão não abre exceções para escopo local se o identificador começar com sublinhado e letra maiúscula. O compilador tem todo o direito de reservar esses nomes para seus próprios fins.
\n\nErro nº3: Sublinhado no início + letra minúscula no escopo global
\n\nRegra: Um identificador que começa com sublinhado, seguido por uma letra minúscula (a-z), é reservado para a implementação no escopo global (namespace scope).
\n\n// RUIM! No espaço de nomes global\nint _myGlobal = 0;\nvoid _helperFunction();\n\n// BOM — dentro de uma função ou classe\nclass MyClass {\n int _memberVar; // Dentro da classe — pode (mas é melhor m_)\n};\n\nvoid foo() {\n int _localVar = 5; // Dentro da fun