C++ НОТАЦІЇ

Коли говорять про «нотацію в C++», зазвичай мають на увазі нотації іменування (naming conventions) — тобто правила та стилі, за якими розробники дають назви змінним, функціям, класам та іншим елементам у коді.

Сама мова C++ не змушує вас використовувати якийсь конкретний стиль, але дотримання певної нотації робить код зрозумілим, чистим і легким для читання іншими програмістами.

Ось основні види нотацій, які використовуються в C++:

1. CamelCase (Верблюжа нотація)

Слова пишуться разом, а кожне нове слово починається з великої літери (це нагадує горби верблюда). Вона ділиться на два типи:

  • Lower CamelCase: перша літера першого слова маленька, всі наступні слова — з великої. Часто використовується для змінних та методів.
  • Upper CamelCase (або PascalCase): ВСІ слова починаються з великої літери. У C++ це стандарт де-факто для назв класів та структур.
// Lower CamelCase
int userAge = 25;
void calculateTotalSum();

// Upper CamelCase (або PascalCase)
class BankAccount;
struct PlayerProfile;

2. snake_case (Зміїна нотація)

Усі слова пишуться маленькими літерами й розділяються символом підкреслення (_). Це офіційний стиль самої стандартної бібліотеки C++ (STL). Якщо ви подивитеся на вбудовані функції на кшталт std::vector::push_back() або типи даних на кшталт std::string, ви побачите саме цей стиль.

int max_value = 100;
void print_user_name();

3. Hungarian Notation (Угорська нотація)

Особливий стиль, де до початку назви змінної додається спеціальний префікс (одна або кілька маленьких літер), який вказує на її тип або призначення.

  • i або n — для цілих чисел (int)
  • f — для чисел з рухомою комою (float)
  • p — для вказівників (pointer)
int iAge = 30;         // і — означає int
float fPrice = 19.99f; // f — означає float
int* pNumber = &iAge;  // p — означає вказівник (pointer)

Сучасний статус: Сьогодні угорська нотація вважається застарілою. Сучасні середи розробки (IDE) легко показують тип змінної, якщо просто навести на неї мишку, тому засмічувати ім’я префіксами більше немає сенсу. Проте її все ще можна зустріти в старому коді або в API від Microsoft (Windows API).

4. SCREAMING_SNAKE_CASE (Для констант)

Усі літери великі, слова розділяються підкресленням. Використовується для констант та макросів препроцесора, щоб їх було одразу помітно в коді.

const double PI = 3.14159;

#define MAX_BUFFER_SIZE 1024

Яку нотацію обрати?

Головне правило в C++ — послідовність. Неважливо, оберете ви camelCase чи snake_case, головне — не змішувати їх в одному проєкті.

Сьогодні більшість великих компаній мають свої відкриті правила (Style Guides). Наприклад:

  • Google Style Guide для C++ рекомендує використовувати snake_case для змінних, PascalCase для класів, і додавати підкреслення в кінці для приватних змінних класу (наприклад, my_private_var_).
  • Стандартна бібліотека (STL) повністю побудована на snake_case.

📘 Стандарти C++ іменування та форматування

📁 Іменування файлів та структура заголовків

Усі назви файлів пишуться виключно маленькими літерами. Окремі слова розділяються символом підкреслення _.

  • Заголовні файли мають розширення: .hpp
  • Файли реалізації мають розширення: .cpp

Plaintext

📁 Приклади назв файлів:
├── header_file.hpp
├── source_file.cpp
├── my_complex_class.hpp
└── my_complex_class.cpp

Директива #include та логічні групи

Якщо шлях містить каталоги, використовуйте тільки прямий слеш (/). Різні логічні групи заголовних файлів обов’язково відокремлюються порожнім рядком.

C++

#include <stdafx.h>
#include <string>
#include <vector>
#include <auto_ptr_2/h/vect_ptr.hpp>
#include <auto_ptr_2/h/obj_ptr.hpp>
#include <simexpl_1/h/log_macros.hpp>
#include <simexpl_1/h/card_representation.hpp>

🏷️ Правила іменування сутностей

Для більшості елементів базовим є snake_case (маленькі літери + підкреслення). Проте кожна сутність має свої специфічні префікси чи суфікси.

1. Простори назв (Namespaces)

  • Тільки маленькі літери. Окремі слова або самостійні частини назв розділяються підкресленням.
  • Великі літери дозволені виключно для загальноприйнятих абревіатур (SNMP, HTTP, SMTP).
  • Після закриваючої фігурної дужки обов’язково пишеться коментар із назвою простору.

C++

namespace oess_sequence_1 
{
    namespace impl 
    {
        // Логіка модуля
    } /* namespace impl */
} /* namespace oess_sequence_1 */

2. Типи даних (Класи, структури, аліаси)

  • Завжди завершуються суфіксом _t.
  • Для вказівників на функції використовується префікс pfn_ та суфікс _t.
  • Для типів виключень (Exceptions) використовується суфікс _ex_t.

C++

struct my_struct_t { ... };
typedef unsigned char uint8_t;

class io_subsystem_t { ... };
class io_subsystem_socket_tcp_t : public io_subsystem_t { ... };

// Вказівник на функцію та виключення
typedef uint8_t ( *pfn_some_function_t )();
class file_not_found_ex_t : public std::exception { ... };

3. Функції та методи

  • Пишуться з маленької літери. Повертаєме значення (якщо назва типу довга) краще виносити на окремий рядок.
  • Функції без аргументів записуються як f(), а не f(void).
  • Глобальні або вільні функції можуть мати суфікс _ для відмінності від методів класів.

C++

// Вільна функція з переносом типу
void
init_io_subsystem_();

// Метод класу
class some_class_t {
public:
    void
    some_method();
};

💡 Геттери та Сеттери: Замість класичної пари get_ / set_ використовуйте лаконічніший підхід: назва властивості для читання та set_ для запису.

  • const std::string & font_name() const;Рекомендовано (коротко)
  • void set_font_name( const std::string & name );

4. Змінні та поля класів

🚫 Заборона: Угорська нотація (додавання типу змінної в її назву на кшталт iAge, fPrice) НЕ використовується.

  • Локальні змінні / аргументи: Звичайна зміна в стилі snake_case.
  • Глобальні змінні: Можуть мати префікс g_. Він необов’язковий, якщо контекст очевидний (наприклад, всередині окремого namespace cfg).
  • Поля класів та структур: Обов’язково починаються з префікса m_ (member).

C++

// Поля структури та класу

struct info_t {
    int m_id;
    std::string m_full_name;
};

// Глобальні змінні
my_class_t g_my_class; 

namespace cfg {
    configuration_t current; // префікс g_ не потрібен завдяки namespace
}

5. Константи та Елементи перерахувань (Enum)

  • Константи: Можуть мати префікс c_. Якщо константи згруповані всередині namespace (наприклад, коди помилок), префікс можна опустити.
  • Елементи enum: Починаються з префікса e_. Якщо перерахування лежить всередині класу, префікс e_ можна опустити, оскільки доступ відбувається через ім’я класу.

C++

const size_t c_max_size = 15;

enum error_t {
    e_none,
    e_bad_argument
};

// Контекст класу прибирає потребу в префіксах
class gsm_sms_text_t {
public:
    enum data_coding_scheme_t {
        default_coding_scheme,
        general_coding_scheme
    };
};

📐 Довжина рядка, відступи та дужки

Правила перенесення рядків

  • Максимальна довжина рядка — 70 символів. Оптимально: 60–65 символів.
  • Відступи формуються виключно за допомогою табуляції (Tab). Розмір табуляції — 4 символи.
  • При переносі довгого рядка наступний рядок отримує додатковий відступ в 1 або 2 таби.

НЕПРАВИЛЬНО (Вирівнювання по скобках/тексту засмічує код):

C++

// Погана практика вирівнювання "ялинкою"
void some_class_t::some_method( some_type1_t & arg_type1,
                                some_type2_t & arg_type2 );

✔️ ПРАВИЛЬНО (Зручно читати вертикально):

C++

void some_class_t::some_method(
    some_type1_t & arg_type1,
    some_type2_t & arg_type2 );

Приклади правильних відступів (Стилі дужок та Switch)

C++

// Варіант зі стандартним або GNU-відступом для inline-функцій

inline type_with_long_name_t *
some_inline_fuction( int arg ) {
    return new type_with_long_name( arg );
}

// Форматування оператора switch
switch( operation_type ) {
    case c_op_type1: {
        call_op_type1_handler();
    }
    break;
    case c_op_type2: {
        call_op_type2_handler();
    }
    break;
}

Подвійний відступ (2 Таби)

Використовується у складних умовах if, for, while, щоб візуально відокремити умову від тіла оператора, який йде нижче:

C++

// Подвійний таб чітко показує, де закінчується умова і починається throw

if( trace_no != config.trace_mode() &&
        trace_last != config.trace_mode() &&
        trace_all != config.trace_mode() )
    throw std::logic_error( "unsupported trace mode" );

// Одразу видно, де тіло циклу
for( my_object_map_t::const_iterator it = m_map.begin();
        it != m_map.end();
        ++it )
    it->second.do_calculation( config, params, result );

⌨️ Пробелы та правила їх розстановки

  1. Пробелів має бути багато. Кожен значущий елемент виразу виділяється пробелами.
  2. Поспіль не повинно бути двох або більше пробелів (окрім табів відступу).
  3. Ніяких пробелів між ідентифікатором (назвою функції/ключовим словом) та відкриваючою круглою дужкою.
  4. Обов’язкові пробели після відкриваючої та перед закриваючою круглою дужкою.
  5. Виключення: Приведення типів у стилі C (type)var пишеться без внутрішніх пробелів.

C++

void my_func_( file_name_manager_t * file_name_manager, void * data ) {
    FILE * my_file;
    // Зверніть увагу на пробели біля дужок у if та fopen
    if( 0 != ( my_file = fopen(
        file_name_manager->query_name( c_my_file_name ),
        file_name_manager->query_open_mode( c_my_open_mode ) ) ) ) {
     
        try {
            // Приведення типу в стилі C — без внутрішніх пробелів
            my_data_object_t * data_obj = (my_data_object_t *)data;
            data_obj->save_data( my_file );
        }
        catch( invalid_data_t & x ) {
            // Обробка помилки
        }
        fclose( my_file );
    }
}

💬 Коментарі та проектування класів

  • Довжина коментарів підпорядковується загальному правилу — не більше 70 символів у рядку.
  • Коментарі мають йти перед кодом або дією, а не праворуч чи знизу від них.
  • Великі багаторядкові коментарі використовуються переважно для глобальних речей (наприклад, детальний опис призначення всього класу).
  • 🚫 Тимчасове видалення коду: Для відключення великих шматків коду під час відладки використовуйте директиви #if 0 та #endif, а не коментування (/* ... */).

Архітектура та порядок у класі

Для нешаблонного класу намагайтеся дотримуватися правила мінімальної кількості секцій: одна public, одна protected та одна private.

C++

class perfect_class_t {
public:
    // 1. Статичні змінні
    static int m_global_counter;

    // 2. Конструктори та деструктори
    perfect_class_t();
    ~perfect_class_t();

    // 3. Інші методи (бажано в тому ж порядку, що й у файлі реалізації .cpp)
    void execute();

protected:
    std::string m_internal_status;

private:
    int m_id;
};

1. Коментарі (Comments)

Головний принцип — коментарі мають бути акуратними, легко читатися і не створювати «хаосу» всередині коду.

  • Обмеження щодо довжини: Текст коментаря не повинен перевищувати максимальну довжину рядка (зазвичай це 80 або 120 символів, залежно від стандартів вашої команди). Якщо думка довга, переносьте її на новий рядок.
  • Розташування: Пишіть коментар перед кодом, якого він стосується. Жодних коментарів праворуч від коду (в тому самому рядку) або знизу під ним.
  • Багаторядкові коментарі (/* ... */): Використовуйте їх для великих, розгорнутих описів. Це ідеальний інструмент для документації класів, складних алгоритмів або архітектурних модулів.
  • Відключення коду під час зневадження (отладки): Ніколи не використовуйте коментарі, щоб «сховати» старий або непотрібний код під час пошуку помилок. Для цього існують директиви препроцесора #if 0 та #endif. Компілятор гарантовано пропустить цей блок, а вам згодом буде простіше його знайти та повернути (або остаточно видалити).

C++

// ==== ПРАВИЛЬНО ====

/*
 * Клас UserManager відповідає за реєстрацію, автентифікацію
 * та збереження сесійних даних користувачів системи.
 * Підтримує кешування для прискорення повторного доступу.
 */
class UserManager {
    // Перевірка прав доступу перед виконанням операції
    void validateAccess(); 
};

void debugFunction() {
#if 0
    // Цей код тимчасово вимкнено для зневадження.
    // Він не буде компілюватися, але структура збережеться.
    criticalOldCode();
    executeLegacyPipeline();
#endif
}


// ==== НЕПРАВИЛЬНО ====

class Item {
    void process(); // Навішуємо обробник на предмет (НЕ ТРЕБА ТАК: коментар праворуч)
    
    void update();
    // Оновлення стану предмета (НЕ ТРЕБА ТАК: коментар знизу)
};

void badDebug() {
    // void oldFunction();  <- НЕ ТРЕБА ТАК: використання коментарів
    // doSomethingElse();   <- для вилучення коду під час зневадження
}

2. Спосіб опису та реалізації класу (Class Structure)

Чиста структура класу дозволяє іншим розробникам миттєво зрозуміти його інтерфейс (що він вміє робити), не відволікаючись на деталі внутрішньої реалізації.

Опис класу (Заголовний файл .h / .hpp)

Щоб не створювати безладу, групуйте рівні доступу. В ідеалі у класу (якщо він не шаблонний і не містить складних вкладених типів) має бути не більше однієї секції кожного типу, розташованих у логічному порядку: спочатку public, потім protected (якщо є), і наприкінці private.

C++

// Юніт-файл: Device.h
class Device {
public:
    // Усі доступні ззовні методи та конструктори — в одній секції
    Device();
    ~Device();

    void turnOn();
    void turnOff();

protected:
    // Внутрішні інструменти для класів-нащадків
    void ChangeVoltage();

private:
    // Інкапсульовані дані та утиліти самого класу
    static int deviceCount;
    bool isRunning;
    
    void internalReset();
};

Реалізація класу (Файл вихідного коду .cpp)

Під час написання самого коду (реалізації методів) суворо дотримуйтесь такої послідовності:

  1. Оголошення (ініціалізація) статичних змінних.
  2. Конструктори та деструктори.
  3. Решта методів класу.

Важливо: Порядок реалізації методів у .cpp файлі має точно повторювати порядок їхнього декларування в описі класу (у .h файлі), якщо це дозволяє ваше середовище розробки. Це значно спрощує навігацію кодом.

C++

// Юніт-файл: Device.cpp
#include "Device.h"

// 1. Статичні змінні
int Device::deviceCount = 0;

// 2. Конструктори та деструктори
Device::Device() : isRunning(false) {
    deviceCount++;
}

Device::~Device() {
    deviceCount--;
}

// 3. Решта методів (у тому самому порядку, що й в описі класу!)
void Device::turnOn() {
    isRunning = true;
}

void Device::turnOff() {
    isRunning = false;
}

void Device::ChangeVoltage() {
    // Реалізація protected-методу
}

void Device::internalReset() {
    // Реалізація private-методу
}