struct против class в C++: где разница реальна, а где миф
На собеседованиях любят спрашивать, чем struct отличается от class в C++. Правильный ответ короче, чем кажется, а практические последствия — глубже, чем «одно для данных, другое для логики».

Откройте стандарт C++ (любой актуальной редакции — C++11 и новее) и найдите определения struct и class. Разница между ними умещается в одну строку: у struct члены и базовые классы по умолчанию public, у class — private. Всё. Больше язык не делает между ними никаких различий. Ни в размере объектов, ни в способности иметь методы, конструкторы, виртуальные функции или наследоваться. Тем не менее выбор ключевого слова влияет на читаемость кода, на работу с шаблонами и на то, как вас поймёт код-ревьюер.
Что на самом деле различается
Разница проявляется ровно в двух местах.
Видимость членов по умолчанию. Если вы объявили член без модификатора доступа, в struct он публичный, в class — приватный.
// C++11 и новее
struct Point {
int x; // public по умолчанию
int y; // public
};
class Vector {
int x; // private по умолчанию
int y; // private
public:
Vector(int ax, int ay) : x(ax), y(ay) {}
int getX() const { return x; }
};
Объект Point можно инициализировать агрегатно: Point p{1, 2};. С Vector так нельзя — приватные поля закрыты, и класс перестаёт быть агрегатом.
Видимость при наследовании. Тот же принцип действует и для базового класса.
struct Base {};
// эквивалентно : public Base
struct DerivedS : Base {};
// эквивалентно : private Base
class DerivedC : Base {};
Для class DerivedC : Base наследование приватное. Это редкий источник ошибок: разработчик пишет class Widget : Base, ожидая, что Widget — это Base, а получает приватное наследование, и полиморфизм не работает. Компилятор промолчит, зато при попытке передать Widget* туда, где ждут Base*, выдаст ошибку преобразования.
Что НЕ различается — список распространённых мифов
Половина ответов на собеседованиях — про несуществующие различия. Разберём, что одинаково у обоих:
- Методы. И
struct, иclassмогут иметь функции-члены, включая виртуальные. - Конструкторы и деструкторы. Пишутся одинаково, работают одинаково.
- Наследование. Оба наследуются и наследуются от, включая множественное.
- Инкапсуляция. В
structможно написатьprivate:и получить полностью закрытые поля. - Размер и раскладка в памяти. При одинаковом наборе полей и модификаторов размер объектов совпадает байт в байт.
- Шаблоны. Шаблонными могут быть оба.
Отдельный миф — что struct в C++ это «то же, что в C». Нет. C-шный struct не имеет методов, наследования и модификаторов доступа. C++-ный struct — полноценный тип с одним отличием от class.
Единственная гарантированная разница между struct и class — умолчание видимости. Всё остальное, что вам про них рассказывают, — это соглашения команды, а не правила языка.
Один случай, где ключевое слово обязано совпадать
При предварительном объявлении (forward declaration) и при явном инстанцировании шаблонов некоторые компиляторы под определёнными настройками предупреждают о несовпадении тегов. Стандарт считает struct Foo; и class Foo; ссылками на один и тот же тип, но MSVC исторически выдаёт предупреждение C4099, если в объявлении и определении теги разные. GCC и Clang по умолчанию молчат.
// заголовок
class Session; // объявили как class
// реализация
struct Session { // определили как struct — MSVC ругнётся C4099
// ...
};
Практический вывод: держите тег одинаковым в объявлении и определении. Это не требование стандарта, но экономит время на разборе предупреждений сборки под разными компиляторами.
Когда что выбирать
Раз язык различий почти не делает, выбор — это стилевое соглашение. Самое распространённое, зафиксированное в том числе в C++ Core Guidelines, звучит так.
| Ситуация | Выбор | Почему |
|---|---|---|
| Пассивный набор данных без инвариантов между полями | struct | Поля публичны, читатель видит «это просто данные» |
| Тип с инвариантом (поля связаны, их нельзя менять независимо) | class | Приватные поля по умолчанию, доступ через методы |
| Возврат нескольких значений из функции | struct | Агрегатная инициализация, никакой обвязки |
| Функтор, трейт, тег-тип в шаблонах | struct | Всё публично, меньше шума в коде |
| Класс с ресурсом (владеет памятью, дескриптором, соединением) | class | Инкапсуляция и правило пяти уместны здесь |
Критерий Core Guidelines сформулирован коротко: используйте class, если у типа есть инвариант; используйте struct, если поля могут меняться независимо друг от друга. Инвариант* здесь — правило, которое всегда должно быть истинным для корректного объекта.
Пример с инвариантом
// Дата: месяц и день не бывают произвольными — это инвариант
class Date {
int year;
int month; // 1..12
int day; // зависит от месяца и года
public:
Date(int y, int m, int d); // проверяет корректность в теле
int getYear() const { return year; }
};
// Пара координат: x и y меняются независимо, инварианта нет
struct Point {
double x;
double y;
};
Если бы Date был struct с публичными полями, любой код мог бы записать d.month = 13 и сломать объект. Приватные поля class делают такое присваивание невозможным без метода-валидатора.
Типичные ошибки
- Ожидание публичного наследования от
class.class D : Base— приватное наследование. Пишитеclass D : public Baseявно. - Публичные поля с попыткой поддерживать инвариант. Если поля публичны, инвариант не удержать: любой внешний код их перезапишет. Либо инвариант, либо публичные поля.
- Смешение тегов в объявлении и определении — предупреждение C4099 в MSVC.
- Потеря агрегатной инициализации. Добавили в
structприватное поле или пользовательский конструктор —Point p{1, 2}перестанет компилироваться как агрегат. Правила агрегатной инициализации менялись между C++11, C++14, C++17 и C++20 — сверяйтесь с реальной версией стандарта, под которую собираете.
Что делать дальше
Заведите в проекте одно соглашение и зафиксируйте его в стайл-гайде: struct — для агрегатов данных без инвариантов, class — для всего с инкапсуляцией. На код-ревью проверяйте не выбор ключевого слова сам по себе, а совпадение слова с намерением: публичные поля рядом с валидацией — повод задать вопрос. Если работаете с шаблонами, загляните в разделы стандарта про агрегатную инициализацию для вашей версии C++ — именно там струкут ведут себя не так, как ожидают привыкшие к классам.
HR-директор: Вакансии, анализ резюме, вопросы для технических собеседований.
Спросить за 20 ₽Источники: C++ Core Guidelines — C.2 и C.8 (struct vs class), cppreference — Classes (access specifiers, aggregates)
Частые вопросы
Есть ли разница в производительности между struct и class?
Можно ли в struct сделать поля приватными?
А если у меня старый стандарт C++98 — различия те же?
Почему мой class не наследуется полиморфно, хотя я написал class D : Base?
Компилятор ругается предупреждением C4099 — это ошибка?
Что выбрать для возврата нескольких значений из функции?
Материал носит информационный характер и подготовлен редакцией «Агентуры». Он не является офертой, рекламой или индивидуальной консультацией. Упомянутые продукты, компании и торговые знаки принадлежат их правообладателям. Перед принятием решений, влекущих юридические или финансовые последствия, обратитесь к профильному специалисту.