Avatar ·

Когда в C++ использовать class, а когда struct? Правила и лучшие практики

Всем привет! Я недавно начал изучать C++ и запутался с выбором между class и struct. Вроде бы в C++ они почти одинаковые, но я вижу, что опытные программисты используют их по-разному. Например, я написал такой код для точки на плоскости: ```cpp struct Point { int x; int y; }; class Circle { private: int radius; public: Circle(int r) : radius(r) {} int getRadius() { return radius; } }; ``` Но потом я увидел, что кто-то делает так: ```cpp class Point2 { public: int x; int y; }; ``` И мне стало непонятно — зачем использовать class, если всё равно всё публичное? И наоборот, почему бы не сделать struct с приватными полями и методами? Я слышал, что есть какое-то "правило большого пальца" (rule of thumb), но не могу найти чёткого объяснения. Когда я должен выбирать struct, а когда class? Есть ли какие-то конвенции или это просто дело вкуса? Буду благодарен за развёрнутый ответ с примерами! <<>>

Основное различие: модификаторы доступа по умолчанию

Технически в C++ между class и struct есть только одно различие: модификатор доступа по умолчанию.

  • struct: все члены и базовые классы по умолчанию public.
  • class: все члены и базовые классы по умолчанию private.

Это означает, что следующие два определения абсолютно идентичны по поведению:

// Вариант 1: struct
struct Point {
    int x;
    int y;
    void print() { /* ... */ }
};

// Вариант 2: class
class Point {
public:  // явно указываем public
    int x;
    int y;
    void print() { /* ... */ }
};

Правило большого пальца (Rule of Thumb)

В сообществе C++ сложились следующие негласные конвенции:

  • Используйте struct для пассивных структур данных (POD — Plain Old Data). Это объекты, которые просто хранят данные и не имеют инвариантов (сложной логики проверки). Примеры: точки, векторы, цвета, конфигурационные параметры.
  • Используйте class для объектов с инвариантами — когда есть логика, защищающая целостность данных (инкапсуляция).

Практические примеры

Когда struct уместен:

// Простая структура данных — все поля открыты
struct Color {
    unsigned char r, g, b, a;
};

struct Vec2 {
    float x, y;
    Vec2 operator+(const Vec2& other) const {
        return {x + other.x, y + other.y};
    }
};

Когда class обязателен:

// Класс с инвариантами — радиус не может быть отрицательным
class Circle {
private:
    double radius;
public:
    Circle(double r) {
        if (r < 0) throw std::invalid_argument("Radius must be non-negative");
        radius = r;
    }
    double area() const { return 3.14159 * radius * radius; }
    // Нет прямого доступа к radius — только через методы
};

Почему это важно?

Использование struct для простых данных — это сигнал другим разработчикам: "Здесь нет магии, просто данные". Когда вы видите struct, вы знаете, что можете свободно читать и изменять поля. class же предупреждает: "Осторожно, здесь есть логика, не трогайте внутренности напрямую".

Дополнительные рекомендации

  • Если у структуры есть метод, но он не нарушает инвариант (например, length() для вектора) — это нормально. Главное — отсутствие скрытой логики.
  • Избегайте class с public: в начале — это противоречит цели использования класса.
  • В современном C++ (C++17 и новее) для простых структур часто используют struct с агрегатной инициализацией: Point p{3, 4};
<<>>

Альтернативный взгляд: не зацикливайтесь на синтаксисе

Некоторые разработчики считают, что различие между class и struct — это просто вопрос стиля, и в больших проектах это не имеет значения. Однако я предлагаю другой подход: думайте о семантике, а не о синтаксисе.

Вместо того чтобы гадать "struct или class", задайте себе вопрос: "Что этот тип представляет?"

  • Если это значение (value type) — данные, которые можно копировать и сравнивать — используйте struct. Примеры: координаты, дата, деньги.
  • Если это сущность (entity type) — объект с идентичностью и поведением — используйте class. Примеры: пользователь, заказ, соединение с базой данных.

Заметьте: даже если у структуры есть методы (как у std::complex), она остаётся структурой, потому что это математическое значение. А класс может быть почти пустым (как пустой интерфейс), но всё равно быть классом.

Этот подход хорошо ложится на принципы DDD (Domain-Driven Design) и помогает писать более понятный код. Главное — быть последовательным в рамках одного проекта.