базовые концепции ООП — вложенные классыbaseline OOP concepts — nested classes

Вложенные классыNested classes

Класс, объявленный внутри другого класса, — в первую очередь механизм пространства имён и видимости, а не отношение владения между объектами: вложенность в объявлении не означает, что объект вложенного класса физически находится внутри объекта внешнего. Типичное применение — вспомогательные типы, не имеющие смысла вне контекста внешнего класса (итераторы, узлы связного списка, конфигурационные структуры).A class declared inside another class is primarily a namespace and visibility mechanism, not an ownership relationship between objects: nesting in the declaration doesn't mean an object of the nested class physically lives inside an object of the outer one. The typical use is helper types that make no sense outside the outer class's context (iterators, linked-list nodes, configuration structs).

пространство имён, не владениеnamespacing, not ownership

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

Вложенный класс — класс, объявленный в теле другого класса. Его полное имя вне внешнего класса требует квалификации через ::, как у любого другого имени в пространстве имён. Наличие вложенного класса ничего не говорит о layout объектов в памяти — объект внешнего класса не обязан (и обычно не должен) содержать объект вложенного как поле, если это не объявлено отдельно.A nested class is a class declared inside another class's body. Its full name outside the outer class requires :: qualification, just like any other namespaced name. Having a nested class says nothing about object memory layout — the outer class's object isn't required to (and usually doesn't) contain an object of the nested class as a field unless that's declared separately.

class LinkedList {
    struct Node {              // имеет смысл только в контексте LinkedList// only makes sense in LinkedList's context
        int value;
        Node* next = nullptr;
    };
    Node* head = nullptr;
public:
    void push_front(int v) { head = new Node{v, head}; }
};

// вне LinkedList: LinkedList::Node — доступно, только если Node сделан public// outside LinkedList: LinkedList::Node — accessible only if Node is made public

Видимость имени только внутри объемлющего классаName visibility scoped to the enclosing class

Имя вложенного класса без квалификации видно только внутри тела внешнего класса — снаружи требуется Outer::Inner. Уровень доступа (public/protected/private) вложенного класса контролирует именно это — может ли внешний код вообще ссылаться на имя Outer::Inner, независимо от прав доступа к членам самого Inner.A nested class's unqualified name is visible only inside the outer class's body — outside, Outer::Inner is required. The nested class's access level (public/protected/private) controls exactly that — whether outside code can reference the name Outer::Inner at all, independent of access rights to Inner's own members.

class Parser {
private:
    struct Token { std::string text; int line; };   // private вложенный класс// a private nested class
    std::vector<Token> tokens;   // внутри Parser имя Token видно без квалификации// inside Parser the name Token is visible unqualified
public:
    size_t tokenCount() const { return tokens.size(); }
};

// Parser::Token t; — ошибка компиляции: Token приватен, внешний код не может даже назвать этот тип// Parser::Token t; — compile error: Token is private, outside code can't even name this type

Доступ к static-членам внешнего классаAccess to the outer class's static members

Методы вложенного класса имеют доступ к private static-членам внешнего класса (и наоборот — внешний класс имеет доступ к private-членам вложенного). Это единственная привилегия, которую вложенность даёт помимо пространства имён — в остальном вложенный класс не имеет никакого особого доступа к нестатическим полям конкретного объекта внешнего класса, потому что у него просто нет неявной ссылки на такой объект.A nested class's methods have access to the outer class's private static members (and vice versa — the outer class has access to the nested class's private members). This is the one privilege nesting grants beyond namespacing — otherwise, a nested class has no special access to any specific outer-class object's non-static fields, because it simply has no implicit reference to such an object.

class ConnectionPool {
    static inline int active_count = 0;   // private static — счётчик на весь класс// private static — a counter for the whole class

    class Connection {
    public:
        Connection() { active_count++; }    // доступ к private static внешнего класса — разрешён// access to the outer class's private static — allowed
        ~Connection() { active_count--; }
    };
public:
    static int activeCount() { return active_count; }
};

Обратная сторона: Connection внутри ConnectionPool не получает никакого автоматического доступа к конкретному объекту ConnectionPool — если такой доступ нужен, вложенный класс должен явно хранить указатель/ссылку на внешний объект как обычное поле, точно так же, как это делал бы любой независимый класс.The flip side: Connection inside ConnectionPool gets no automatic access to any specific ConnectionPool object — if that access is needed, the nested class must explicitly store a pointer/reference to the outer object as an ordinary field, exactly as any unrelated class would.

ГраблиGotchas

Вложенность — не то же самое, что "внутренний класс" из Java/C#Nesting isn't the same as a Java/C# "inner class"

В Java нестатический внутренний класс неявно хранит ссылку на объект внешнего класса, создавшего его. В C++ ничего подобного не происходит автоматически — любая связь между объектом вложенного класса и конкретным объектом внешнего класса должна быть спроектирована и написана явно.In Java, a non-static inner class implicitly holds a reference to the outer-class object that created it. Nothing like that happens automatically in C++ — any link between a nested-class object and a specific outer-class object has to be designed and written explicitly.

Вложенный класс может использоваться до полного определения внешнего в некоторых контекстахA nested class can sometimes be used before the outer class is fully defined

Определение вложенного класса за пределами тела внешнего (void Outer::Inner::method() {…}) требует, чтобы Outer и Outer::Inner были уже объявлены (не обязательно полностью определены) в точке использования — источник не всегда очевидных ошибок компиляции про неполный тип при попытке слишком рано использовать вложенный класс в объявлениях, использующих его по значению.Defining a nested class's method outside the outer class's body (void Outer::Inner::method() {…}) requires that Outer and Outer::Inner already be declared (not necessarily fully defined) at the point of use — a source of not-always-obvious "incomplete type" compile errors when a nested class is used by value too early in declarations.

Локальный класс (внутри функции) — ещё более узкая вложенностьA local class (inside a function) — an even narrower kind of nesting

Класс можно объявить прямо внутри тела функции — его видимость ограничена этой функцией, он не может иметь static-члены с определением вне класса и не может использоваться как аргумент шаблона до C++11. На практике почти всегда предпочтительнее лямбда-выражение, если не нужно именно несколько методов и полей вместе.A class can be declared right inside a function body — its visibility is limited to that function, it can't have static members defined outside the class, and it couldn't be used as a template argument before C++11. In practice a lambda is almost always preferable unless several methods and fields together are genuinely needed.