базовые концепции ООП — члены типаbaseline OOP concepts — type members

Члены типаType Members

Класс может объявлять не только поля и методы, но и типы, логически принадлежащие ему, — псевдонимы, перечисления, вложенные классы. Это не синтаксический сахар, а основа того, как STL и обобщённый код в C++ узнают друг о друге через стандартизированные имена вроде value_type и iterator.A class can declare not just fields and methods, but types that logically belong to it too — aliases, enumerations, nested classes. This isn't syntactic sugar — it's the basis of how the STL and generic C++ code discover things about each other through standardized names like value_type and iterator.

частый вопросcommon interview question

using-алиасы как члены классаaliases as class members

using Name = Type; внутри класса вводит псевдоним типа, доступный как ClassName::Name — то же самое, что делает старый синтаксис typedef, но с более читаемым порядком (имя слева) и, в отличие от typedef, поддерживающий шаблонные алиасы.using Name = Type; inside a class introduces a type alias accessible as ClassName::Name — the same thing the older typedef syntax does, but with more readable ordering (the name on the left) and, unlike typedef, support for templated aliases.

template<typename T>
class RingBuffer {
public:
    using value_type = T;                    // стандартизированное имя, которое ждёт обобщённый код// a standardized name generic code looks for
    using size_type  = std::size_t;
private:
    std::vector<T> data;
};

// обобщённая функция узнаёт тип элемента через member alias, не через шаблонный параметр напрямую// generic code discovers the element type via the member alias, not the template parameter directly
template<typename Container>
void describe() {
    typename Container::value_type sample{};
}

Именно эта идиома лежит в основе того, как STL-алгоритмы работают с любым контейнером одинаково: std::vector<T>::value_type, std::list<T>::value_type и любой пользовательский контейнер с тем же именем члена типа взаимозаменяемы для шаблонного кода, который на это имя полагается.This exact idiom is what lets STL algorithms work uniformly with any container: std::vector<T>::value_type, std::list<T>::value_type, and any custom container with the same type-member name are interchangeable for template code relying on that name.

Вложенные typedef и совместимость со старым кодомNested typedefs and legacy compatibility

typedef внутри класса делает ровно то же самое, что using, — объявляет псевдоним, видимый как член класса. Единственная причина увидеть его в новом коде — стилистическая инерция или совместимость с кодовой базой на C++03, где using-алиасы ещё не существовали.A typedef inside a class does exactly the same thing as using — it declares an alias visible as a class member. The only reason to see it in new code is stylistic inertia or compatibility with a C++03-era codebase, where using aliases didn't exist yet.

class LegacyContainer {
public:
    typedef int value_type;        // эквивалентно "using value_type = int;"// equivalent to "using value_type = int;"
};

Разница проявляется только для параметризованных псевдонимов: typedef не умеет объявлять шаблон с незаполненными параметрами (нельзя написать typedef std::vector<T> VecT; внутри шаблона так, чтобы T оставался параметром — только using VecT = std::vector<T>; умеет это напрямую), что и было главным практическим стимулом введения using-алиасов в C++11.The difference only shows up for parameterized aliases: typedef can't declare a template with unfilled parameters (you can't write typedef std::vector<T> VecT; inside a template so that T stays a parameter — only using VecT = std::vector<T>; can do that directly), which was the main practical motivation for introducing using aliases in C++11.

enum class как член классаas a class member

Перечисление, логически принадлежащее одному классу (набор состояний, режимов, кодов ошибок, актуальных только в его контексте), естественно объявляется прямо внутри него — область видимости перечисления автоматически становится вложенной в область видимости класса, что снижает риск конфликта имён с другими частями кодовой базы.An enumeration that logically belongs to one class (a set of states, modes, error codes relevant only in its context) is naturally declared right inside it — the enum's scope automatically nests inside the class's scope, reducing the risk of a name clash with other parts of the codebase.

class Connection {
public:
    enum class State { Disconnected, Connecting, Connected, Failed };

    State status() const { return state_; }
private:
    State state_ = State::Disconnected;
};

// доступ снаружи — обязательно через полное имя, enum class не привносит имена в объемлющую область видимости// accessed from outside via the qualified name — enum class doesn't leak names into the enclosing scope
if (conn.status() == Connection::State::Connected) { ... }

Использование именно enum class (а не обычного enum) внутри класса даёт двойную изоляцию имён: сначала областью видимости самого перечисления (обязательный префикс State::), затем — областью видимости класса (обязательный префикс Connection::) — что почти исключает конфликты имён даже при большом количестве похожих enum-ов в разных классах.Using enum class specifically (not a plain enum) inside the class gives double name isolation: first from the enum's own scope (the mandatory State:: prefix), then from the class's scope (the mandatory Connection:: prefix) — which nearly eliminates name clashes even with many similarly-named enums across different classes.

ГраблиGotchas

typename обязателен для зависимых имён типов в шаблонахis required for dependent type names in templates

Внутри шаблонной функции/класса компилятор не может знать заранее, является ли Container::value_type типом или статическим членом-данными (это зависит от того, чем окажется Container при инстанцировании) — без ключевого слова typename перед таким зависимым именем компилятор по умолчанию считает его НЕ типом, что приводит к ошибке компиляции.Inside a template function/class, the compiler can't know in advance whether Container::value_type is a type or a static data member (that depends on what Container turns out to be at instantiation) — without the typename keyword in front of such a dependent name, the compiler assumes by default that it's NOT a type, which causes a compile error.

Обычный enum как член класса всё ещё загрязняет область видимости классаA plain enum as a class member still pollutes the class's scope

В отличие от enum class, обычный enum внутри класса вносит свои значения непосредственно в область видимости класса без префикса имени перечисления (Connection::Connected, а не Connection::State::Connected) — при нескольких обычных enum-ах с пересекающимися именами значений в одном классе это прямой конфликт имён, ещё одна причина всегда предпочитать enum class.Unlike enum class, a plain enum inside a class dumps its values straight into the class's scope with no enum-name prefix (Connection::Connected, not Connection::State::Connected) — with several plain enums sharing overlapping value names in the same class, that's a direct name clash, one more reason to always prefer enum class.

Вложенный тип, объявленный private, недоступен снаружи даже по имениA nested type declared private is inaccessible from outside, even by name

Если псевдоним или enum объявлен в private-секции, попытка сослаться на него снаружи класса (Connection::State) — ошибка доступа, а не просто "тип не найден"; та же логика доступа, что действует для полей и методов, действует и для членов типа.If an alias or enum is declared in a private section, referring to it from outside the class (Connection::State) is an access error, not just "type not found" — the same access rules that apply to fields and methods also apply to type members.