Подчеркивания в C++: неочевидные правила, которые ломают код

Онлайн-тренажер Python для начинающих

Изучайте Python легко и без перегрузки теорией. Решайте практические задачи с автоматической проверкой, получайте подсказки на русском языке и пишите код прямо в браузере — без необходимости что-либо устанавливать.

Начать курс

Подчеркивания в C++: тихие убийцы вашего кода

Представьте ситуацию: вы пишете чистый, красивый C++ код. Компилятор молчит, линтер не ругается, всё работает идеально. А потом — бац! — и на каком-то другом компиляторе или с новой версией стандарта всё ломается без видимой причины. Знакомо?

Чаще всего корень зла кроется в, казалось бы, безобидных символах подчеркивания (_). Эта тема — настоящий "кладбище" для новичков и даже опытных C++ разработчиков. Не зря вопрос на Stack Overflow о правилах использования подчеркиваний набрал десятки тысяч просмотров. Давайте раз и навсегда разберемся, что можно, что нельзя, и как не попасть в ловушку неопределенного поведения (UB).

Почему стандарт C++ так строг к подчеркиваниям?

Корень проблемы — в совместимости и глобальном пространстве имен. Стандартная библиотека C++ активно использует подчеркивания для своих внутренних нужд. Если вы случайно создадите идентификатор, который конфликтует с внутренними именами библиотеки или компилятора, — всё, пиши пропало. Компилятор может молча переопределить вашу переменную, и вы получите неопределенное поведение (Undefined Behavior), которое проявится в самый неподходящий момент.

Стандарт C++ (начиная с C++03 и во всех последующих версиях, включая C++20 и C++23) чётко прописывает, какие идентификаторы являются зарезервированными для реализации. Нарушение этих правил делает ваш код не просто некорректным, а непереносимым.

Топ-5 смертельных ошибок с подчеркиваниями

Разберем самые распространенные грабли, на которые наступают даже матерые программисты.

Ошибка №1: Двойное подчеркивание в любом месте

Правило: Любой идентификатор, содержащий два последовательных символа подчеркивания (__) в любом месте, зарезервирован для реализации.

// ПЛОХО! Зарезервировано для "реализации"
int __myVariable = 42;
void my__function();
class My__Class {};

// ХОРОШО
int myVariable = 42;
void my_function();
class MyClass {};

Почему это опасно? Компилятор может использовать __myVariable для своих внутренних макросов. Например, многие компиляторы определяют __LINE__ или __cplusplus. Если вы создадите своё __LINE__, компилятор может просто проигнорировать ваше определение или, что ещё хуже, заменить его своим значением. Результат — UB.

Ошибка №2: Подчеркивание + заглавная буква в глобальной области

Правило: Идентификатор, начинающийся с подчеркивания, за которым следует заглавная буква (A-Z), зарезервирован для реализации в любой области видимости.

// ПЛОХО! Даже в локальной области!
void foo() {
    int _MyVar = 10; // Нарушение правила
}

// ПЛОХО! Глобально — тем более
int _GlobalVar = 20;

// ХОРОШО
void foo() {
    int myVar = 10;
}

Это правило — самое коварное. Многие думают: "Ну, я же внутри функции, кому это мешает?". Мешает! Стандарт не делает исключений для локальной области видимости, если идентификатор начинается с подчеркивания и заглавной буквы. Компилятор имеет полное право зарезервировать такие имена для своих целей.

Ошибка №3: Подчеркивание в начале + строчная буква в глобальной области

Правило: Идентификатор, начинающийся с подчеркивания, за которым следует строчная буква (a-z), зарезервирован для реализации в глобальной области видимости (namespace scope).

// ПЛОХО! В глобальном пространстве имен
int _myGlobal = 0;
void _helperFunction();

// ХОРОШО — внутри функции или класса
class MyClass {
    int _memberVar; // Внутри класса — можно (но лучше m_)
};

void foo() {
    int _localVar = 5; // Внутри функции — можно
}

// ХОРОШО — в своем пространстве имен
namespace my_lib {
    int _internalVar = 1; // Тоже можно, но не рекомендуется
}

Обратите внимание: внутри класса, структуры, функции или своего пространства имен такое имя НЕ является зарезервированным. Но в глобальном неймспейсе — табу!

Ошибка №4: Подчеркивание в конце — скрытая угроза

Правило: Одиночное подчеркивание в конце идентификатора не является зарезервированным стандартом. Однако это плохая практика и часто конфликтует с соглашениями компиляторов и макросами.

// Формально — можно, но лучше не надо
int value_ = 10;
void process_();

// Почему плохо?
// 1. Многие компиляторы используют суффикс "_" для внутренних символов.
// 2. Это может конфликтовать с ABI (Application Binary Interface).
// 3. Нечитаемо.

// Лучше
int value = 10;
void process();

Исторически сложилось, что некоторые реализации POSIX и системные библиотеки используют конечные подчеркивания. Лучше не рисковать.

Ошибка №5: Подчеркивание как имя

Правило: Одиночный символ подчеркивания _ в качестве идентификатора — это особый случай. В C++11 и новее он может использоваться как заполнитель (placeholder) в некоторых контекстах, но в глобальной области — зарезервирован.

// ПЛОХО в глобальной области
int _ = 100; // Зарезервировано

// ХОРОШО — в некоторых контекстах (C++17+)
for (auto [key, _] : my_map) { // Игнорируем значение
    // ...
}

// ХОРОШО — в лямбдах (C++14+)
auto lambda = [](int _) { return _ + 1; }; // Можно, но сбивает с толку

Использование _ как имени переменной — дурной тон. Это делает код нечитаемым и может привести к путанице при передаче аргументов.

Золотые правила: как не нарушить стандарт

Чтобы никогда не попадать впросак, запомните простую памятку:

  • Никогда не используйте два подчеркивания подряд (__) в любом месте идентификатора.
  • Никогда не начинайте идентификатор с подчеркивания + заглавная буква

Блоги

Рекомендаций по книгам