Класс в C++ — this, static, friend и вложенные типы
:::tip О чём эта статья
Дополнение к ООП в C++: детали объявления класса, которые в учебниках идут сразу после public / private. Здесь — this, статические члены, friend, вложенные и локальные классы. struct, class, union: типы и строки. friend-функции и спецчлены: функции.
:::
Указатель this
Когда вы вызываете метод объекта:
Counter c;
c.increment();
компилятор неявно передаёт в метод адрес текущего объекта — указатель this типа T* (в const-методе — const T*).
class Counter {
int value_ = 0;
public:
Counter& increment() {
++value_; // то же, что ++this->value_
return *this; // вернуть сам объект для цепочки
}
int get() const { return value_; }
};
Counter c;
c.increment().increment(); // value_ станет 2
Указатель this - цепочки и различие полей
| Применение | Пример | Зачем |
|---|---|---|
| Цепочка вызовов | return *this; | obj.a().b().c(); |
| Имя поля = имя параметра | this->name = name; | различить поле и аргумент |
| Передать себя наружу | registry.add(this); | колбэки, регистрация (осторожно с временем жизни!) |
this недоступен в статических методах: у них нет конкретного объекта-получателя.
Статические члены класса
Статическое поле — одна переменная на весь класс, общая для всех экземпляров. Статический метод — функция, которую вызывают как Class::method(), без объекта.
class Session {
static int active_count_;
std::string id_;
public:
Session(std::string id) : id_(std::move(id)) { ++active_count_; }
~Session() { --active_count_; }
static int active_count() { return active_count_; }
};
int Session::active_count_ = 0;
Разбор:
| Элемент | Смысл |
|---|---|
static int active_count_ | объявление: «будет общий счётчик» |
++active_count_ в конструкторе | каждый новый Session увеличивает общий счётчик |
static int active_count() | можно вызвать Session::active_count() без создания Session |
int Session::active_count_ = 0; вне класса | определение и инициализация единственного экземпляра поля (до C++17 обязательно в .cpp) |
C++17 — inline static
class Session {
inline static int active_count_ = 0;
// ...
};
Инициализация прямо в заголовке — удобно для header-only библиотек.
Правила и осторожности
- Статический метод не видит нестатические поля без объекта (
Session s; ...). - Порядок инициализации статических объектов между разными
.cppне гарантирован — см. 1 (static initialization order fiasco). - Счётчики и кэши на класс — нормальная задача для
static. Глобальное состояние всего приложения лучше выносить в namespace или явный singleton с узким API.
Методы inline в определении класса
Метод, написанный внутри объявления класса, по умолчанию inline (для обычных, не шаблонных случаев):
class Point {
int x_, y_;
public:
Point(int x, int y) : x_(x), y_(y) {}
int x() const { return x_; } // inline по умолчанию
};
Слово inline сегодня в первую очередь про ODR (одно определение в нескольких единицах трансляции), а не про обещание «встроить в машинный код» — см. 17. Короткие геттеры в .h — типичный случай.
Дружественные функции и классы (friend)
По умолчанию private и protected видны только методам своего класса. friend открывает доступ конкретной функции или конкретному классу.
friend-функция (часто operator<<)
class Secret {
int code_;
friend std::ostream& operator<<(std::ostream& os, const Secret& s);
public:
explicit Secret(int c) : code_(c) {}
};
std::ostream& operator<<(std::ostream& os, const Secret& s) {
return os << s.code_;
}
operator<< — свободная функция (не метод класса). Без friend она не смогла бы читать code_.
friend-класс
class Engine {
int rpm_ = 0;
friend class EngineTester;
public:
void rev() { rpm_ += 100; }
};
class EngineTester {
public:
static int read_rpm(const Engine& e) { return e.rpm_; }
};
Весь EngineTester видит private у Engine.
Свойства дружбы
- не наследуется: друг
Baseне становится другомDerivedавтоматически; - не транзитивна: если
AдругB, аBдругC,Aне видитC; - ломает инкапсуляцию — применяйте точечно (тесты, один модуль).
Альтернатива: публичный узкий API, метод friendForTesting() под #ifdef TESTING.
Вложенные классы (nested)
Класс внутри другого класса — логическая группировка и доступ к private внешнего:
class HashTable {
public:
class Iterator {
HashTable* table_;
size_t index_;
friend class HashTable;
Iterator(HashTable* t, size_t i) : table_(t), index_(i) {}
public:
bool operator!=(const Iterator& other) const {
return index_ != other.index_;
}
};
Iterator begin();
Iterator end();
};
- Внешний код обращается к типу как
HashTable::Iterator. - Конструктор
Iteratorprivate — создавать итератор может толькоHashTable(черезfriend). - Вложенный тип можно объявить
private, если клиентам нужен толькоIterator, а не деталиHashTable.
Локальные классы
Класс, объявленный внутри функции:
void process() {
class LocalHelper {
public:
static int transform(int x) { return x * 2; }
};
int v = LocalHelper::transform(21);
}
Ограничения:
- тип нельзя использовать снаружи этой функции (шаблоны, заголовки);
- нестатические методы локального класса не вызывают нестатические функции охватывающей функции напрямую (в отличие от лямбды с
[&]); - на практике чаще берут лямбду или класс в анонимном namespace в
.cpp.
Где объявлять класс
| Область | Где объявлен | Кто видит |
|---|---|---|
| Глобальный (namespace) | вне функций | везде, где подключён заголовок |
| Локальный | внутри функции | только эта функция |
| Вложенный | внутри класса | через Outer::Inner |
Класс в анонимном namespace в .cpp скрывает детали модуля (дополняет PIMPL — 30).
Связанные материалы
- ООП в C++ — конструкторы, перегрузка операторов
- Композиция и наследование
- Ключевые слова friend, static
- RTTI
См. также
Другие статьи этого же раздела в боковом меню (как на странице «О разделе»). C++ как язык системного программирования - ключевые принципы, область применения и инженерные требования к коду. Экосистема приложений на C++ - области применения языка от системного ПО до высоконагруженных вычислений. C++ — это мощный язык программирования общего назначения, который обеспечивает прямой доступ к аппаратным ресурсам компьютера при сохранении высокой производительности. Гайд по установке и настройке с написанием первой программы и её запуском. Директива препроцессора include используется для подключения заголовочных файлов в исходный код. Она сообщает компилятору вставить содержимое указанного файла в текущее место перед началом компиляции. Конфигурация — это набор правил и переменных, которые управляют процессом превращения исходного текста в исполняемый продукт. Примеры простых и полезных консольных приложений с демонстрацией концепций языка. Минимальный кроссплатформенный проект C++17 с CMake — структура, сборка и разбор CMakeLists построчно. Модульные тесты с GTest и Catch2 в CMake-проекте — зачем отдельный target, примеры и запуск ctest. Набор мини-проектов для закрепления C++ — консоль, RAII, CMake, Qt, ranges и опционально Vulkan. Набор советов, правил, принципов и обычаев в разработке на этом языке. Типизация, набор правил определения типа данных значений языка.C++ - язык системного программирования
Экосистема приложений на C++
Что требуется знать перед началом изучения языка программирования C++
Первая программа на C++
Начало работы с C++
Конфигурация и сборка в C++
Простые приложения на C++
CMake — первая программа
Google Test и Catch2 в C++
Практические задания по C++
Рекомендации по разработке на C++
Типы данных в C++