базовые концепции ООП — поляbaseline OOP concepts — fields

ПоляFields

Поля — это данные, хранящиеся внутри каждого объекта класса. Три уровня доступа (public/protected/private) реализуют инкапсуляцию на уровне компилятора, а два менее очевидных инструмента — in-class инициализаторы по умолчанию и mutable — решают конкретные практические задачи: не повторять значение по умолчанию в каждом конструкторе и разрешить изменение поля из const-метода.Fields are the data stored inside every object of a class. The three access levels (public/protected/private) implement encapsulation at the compiler level, and two less obvious tools — in-class default member initializers and mutable — solve specific practical problems: not repeating a default value in every constructor, and allowing a field to change from within a const method.

инкапсуляцияencapsulation

ОпределениеDefinition

Поле (member variable, data member) — переменная, объявленная внутри класса, существующая как часть каждого объекта этого класса. В отличие от локальных переменных, время жизни поля привязано ко времени жизни объекта, которому оно принадлежит: поле создаётся при создании объекта и разрушается при его разрушении.A field (member variable, data member) is a variable declared inside a class that exists as part of every object of that class. Unlike local variables, a field's lifetime is tied to the lifetime of the object it belongs to: it's created when the object is created and destroyed when the object is destroyed.

class Timer {
    std::chrono::steady_clock::time_point start;   // поле — часть каждого объекта Timer// a field — part of every Timer object
public:
    Timer() : start(std::chrono::steady_clock::now()) {}
};

public / protected / private

Три уровня доступа определяют, откуда можно обратиться к полю напрямую: public — из любого кода, protected — из методов самого класса и его наследников, private — только из методов самого класса (наследники доступа не имеют).The three access levels control where a field can be accessed directly from: public — from any code, protected — from the class's own methods and its descendants, private — only from the class's own methods (descendants have no access).

public поле — нет инвариантаa public field — no invariant
struct Vec2 { double x, y; };
v.x = 999999;   // любой код может присвоить что угодно — и это нормально для Vec2// any code can assign anything — and that's fine for Vec2
private поле — инвариант защищёнa private field — the invariant is protected
class Percentage {
    double value;   // приватно — нельзя обойти проверку// private — the check can't be bypassed
public:
    void set(double v) { value = std::clamp(v, 0.0, 100.0); }
};

protected — компромисс, часто оказывающийся хуже обоих крайних вариантов: он открывает поле всем нынешним и будущим наследникам, фактически делая его частью публичного контракта класса (любое изменение имени/типа поля ломает все наследники), но не даёт настоящей инкапсуляции. На практике предпочтительнее private поле плюс protected метод-аксессор для наследников.protected is a compromise that often ends up worse than either extreme: it opens the field to every current and future descendant, effectively making it part of the class's public contract (any change to the field's name/type breaks every descendant), without providing real encapsulation. In practice, a private field plus a protected accessor method for descendants is usually preferable.

In-class инициализация по умолчаниюIn-class default member initializers

С C++11 поле можно инициализировать прямо в объявлении внутри класса — значение применяется автоматически к любому конструктору, который не переопределяет это поле в своём списке инициализации. До C++11 то же значение пришлось бы дублировать в списке инициализации каждого конструктора.Since C++11, a field can be initialized right at its declaration inside the class — the value applies automatically to any constructor that doesn't override that field in its own init list. Before C++11, the same value would need to be duplicated in every constructor's init list.

class Connection {
    int timeout_ms = 5000;      // значение по умолчанию — один раз, а не в каждом конструкторе// the default — written once, not in every constructor
    bool use_tls = true;
public:
    Connection() = default;                       // timeout_ms=5000, use_tls=true// timeout_ms=5000, use_tls=true
    Connection(int t) : timeout_ms(t) {}       // timeout_ms=t, use_tls всё равно true// timeout_ms=t, use_tls is still true
};

mutable для const-методовfor const methods

Поле, помеченное mutable, можно изменять даже из const-метода — единственное исключение из правила «const-метод не может менять состояние объекта». Легитимные случаи использования — кэширование результата дорогого вычисления и подсчёт статистики использования (мьютексы для потокобезопасности const-методов, счётчики обращений), то есть изменения, не влияющие на логическое состояние объекта, наблюдаемое извне.A field marked mutable can be changed even from a const method — the one exception to "a const method can't change the object's state." Legitimate uses are caching an expensive computation's result and tracking usage statistics (mutexes for making const methods thread-safe, access counters) — changes that don't affect the object's logical state as observed from outside.

class ExpensiveShape {
    mutable std::optional<double> cached_area;
    double radius;
public:
    double area() const {           // метод логически const — не меняет "форму", только кэширует// logically const — doesn't change the "shape", only caches
        if (!cached_area) cached_area = 3.14159 * radius * radius;
        return *cached_area;
    }
};

Злоупотребление mutable (например, чтобы обойти const-проверку и незаметно изменить наблюдаемое состояние) — прямое нарушение контракта, который const обещает вызывающей стороне: «этот вызов не изменит объект».Abusing mutable (say, to sidestep the const check and quietly change observable state) directly violates the contract const promises the caller: "this call won't change the object."

ГраблиGotchas

protected поля делают рефакторинг иерархии классов хрупкимfields make refactoring the class hierarchy fragile

Изменение protected поля в базовом классе может незаметно сломать код во всех классах-наследниках, разбросанных по кодовой базе или даже в разных библиотеках — эффективно то же самое, что менять публичный API, просто без предупреждения об этом со стороны компилятора на уровне интерфейса.Changing a protected field in a base class can silently break code in every descendant class, scattered across the codebase or even across different libraries — effectively the same as changing a public API, just without the compiler flagging it at the interface level.

Порядок объявления полей определяет и порядок инициализации, и layout в памятиField declaration order determines both initialization order and memory layout

Помимо порядка вызовов конструкторов (разобрано на странице про определение классов), порядок объявления полей также обычно определяет их физический порядок в памяти объекта — компиляторы вправе добавлять padding между полями для выравнивания, поэтому переупорядочивание полей по убыванию размера иногда уменьшает sizeof объекта.Beyond determining constructor call order (covered on the class-definition page), field declaration order also usually determines their physical order in the object's memory — compilers may insert padding between fields for alignment, so reordering fields from largest to smallest can sometimes shrink an object's sizeof.

mutable поле в объекте, захваченном по const-ссылке, всё ещё можно менятьfield in an object captured by const reference can still be changed

const Widget& w не защищает mutable-поля w от изменения — const на ссылке/указателе останавливает изменение обычных полей, но mutable по определению обходит именно эту защиту, независимо от того, через что к объекту обращаются.const Widget& w doesn't protect w's mutable fields from being changed — const on a reference/pointer blocks changes to ordinary fields, but mutable by definition bypasses exactly that protection, regardless of what's used to access the object.