Перейти к основному содержимому

Класс в 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.
  • Конструктор Iterator private — создавать итератор может только 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).


Связанные материалы


См. также

Другие статьи этого же раздела в боковом меню (как на странице «О разделе»).