базовые концепции ООП — члены типаbaseline OOP concepts — 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.
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 внутри класса делает ровно то же самое, что 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.
Перечисление, логически принадлежащее одному классу (набор состояний, режимов, кодов ошибок, актуальных только в его контексте), естественно объявляется прямо внутри него — область видимости перечисления автоматически становится вложенной в область видимости класса, что снижает риск конфликта имён с другими частями кодовой базы.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.
Внутри шаблонной функции/класса компилятор не может знать заранее, является ли 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 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.
Если псевдоним или 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.