Основы программирования на C++: Жизненный цикл объекта
Прежде чем читать мою статью - реши для себя, зачем ты это делаешь. Даже если ты просто нормальный человек, лишним не будет.
Если вы настоящий профессионал программирования, то зачем вы тут сидите и читайте статью на пикабу? Если вы ради интереса зашли почитать, то претензий ноль, но если вы просто захотели задушить нового пользователя Пикабу минусами, то немедленно покиньте статью, вам однозначно интересно не будет.
Здравствуйте, мои маленькие любители программирования!
Жизненный цикл объекта в C++ управляется конструкторами, деструкторами и операторами присваивания. Рассмотрим ключевые аспекты на примере класса Logger, который логирует вызовы своих специальных функций.
Основные этапы жизненного цикла
Создание объекта:
Конструкторы: Вызываются при создании объекта. Могут быть параметризованными, копирующими или перемещающими.
Автоматические объекты (на стеке): Уничтожаются автоматически при выходе из области видимости.
Динамические объекты (в куче): Создаются через new, уничтожаются вручную через delete.
Пример:
Logger x1; // Вызов конструктора без аргументов
Logger* x2 = new Logger(1); // Динамический объект
delete x2; // Вызов деструктора
Копирование и присваивание:
Конструктор копирования: Создает новый объект как копию существующего.
Оператор присваивания: Модифицирует существующий объект.
По умолчанию компилятор генерирует тривиальные версии этих функций.
Пример:
Logger x3 = x1; // Конструктор копирования
x3 = x1; // Оператор присваивания
Перемещение:
Конструктор перемещения и оператор присваивания перемещением позволяют эффективно передавать ресурсы временных объектов.
Используются с std::move для явного указания перемещения.
Пример:
Logger x4 = std::move(x1); // Конструктор перемещения
x4 = Logger(); // Оператор присваивания перемещением
Деструктор: Вызывается при уничтожении объекта. Для автоматических объектов — при выходе из области видимости, для динамических — при вызове delete.
Особенности работы с классами
Статические поля: Общие для всех объектов класса. Используются для подсчета созданных экземпляров или хранения общих данных.
class Logger {
inline static int counter = 0; // Статическое поле
const int id;
public:
Logger() : id(++counter) {} // Инициализация id };
Композиция классов: При создании объекта сначала инициализируются его поля, затем тело конструктора. Деструкторы вызываются в обратном порядке.
class OuterLogger {
Logger inner1, inner2; // Поля инициализируются до тела конструктора
public: OuterLogger() { /* ... */ }
~OuterLogger() { /* ... */ } // Деструкторы inner2, inner1 вызываются после };
Временные объекты и контейнеры
Временные объекты существуют до конца выражения. Могут быть переданы в функции через rvalue-ссылки (&&), что позволяет избежать копирования.
void f(Logger&& x); // Перегрузка для временных объектов
f(Logger()); // Вызов версии с перемещением
Контейнеры (например, std::vector, std::list) управляют памятью автоматически. При реаллокации элементы могут копироваться или перемещаться.
std::vector<Logger> vec;
vec.emplace_back(); // Создание объекта напрямую в контейнере
vec.push_back(Logger()); // Создание временного объекта и его перемещение
Понимание жизненного цикла объектов помогает управлять ресурсами, избегать утечек и оптимизировать производительность.
1. Реализация класса Logger
Создайте класс Logger со следующими требованиями:
Имеет static int counter для подсчета созданных объектов.
Содержит const int id, инициализируемую в конструкторе через ++counter.
Реализуйте:
Конструктор по умолчанию (логирует "Constructor called, id=X").
Конструктор копирования (логирует "Copy constructor, id=X → Y").
Конструктор перемещения (логирует "Move constructor, id=X → Y").
Деструктор (логирует "Destructor, id=X").
Операторы присваивания (копирования и перемещения) с аналогичным логированием.
Цель: Научиться реализовывать все специальные функции класса.
2. Проверка порядка деструкторов
Создайте класс Composite, содержащий два поля типа Logger:
class Composite { Logger logger1; Logger logger2; public: Composite() { /* ... */ } };
В конструкторе Composite добавьте вывод "Composite constructor".
В деструкторе — "Composite destructor".
Создайте объект Composite в области видимости функции и определите порядок вызова деструкторов.
Цель: Понять порядок инициализации и уничтожения полей класса.
3. Временные объекты и перемещение
Напишите функцию:
void processLogger(Logger&& tempLogger) { std::cout << "Processing temporary logger" << std::endl; }
Создайте временный объект Logger() и передайте его в processLogger.
Объясните, почему не вызывается конструктор копирования.
Цель: Закрепить работу с rvalue-ссылками и перемещением.
4. Контейнеры и реаллокация
Создайте std::vector<Logger>.
Добавьте в него 3 элемента через push_back и emplace_back.
Запустите программу и объясните:
Почему при push_back(Logger()) вызывается перемещающий конструктор.
Как emplace_back позволяет избежать лишних вызовов конструкторов.
Что происходит при реаллокации вектора?
Цель: Изучить работу с контейнерами и оптимизацию через перемещение.
5. Динамические объекты и умные указатели
Создайте динамический объект Logger* dynamicLogger = new Logger();.
Удалите его через delete.
Перепишите код с использованием std::unique_ptr<Logger>.
Объясните, как умные указатели предотвращают утечки памяти.
Цель: Научиться управлять динамической памятью.
6. Запрет копирования
Модифицируйте класс Logger:
Удалите конструктор копирования и оператор присваивания.
Проверьте, что код Logger a; Logger b = a; вызывает ошибку компиляции.
Цель: Понять, как ограничивать жизненный цикл объектов.
7. Наследование и порядок вызовов
Создайте класс DerivedLogger : public Logger:
Добавьте поле Logger memberLogger.
В конструкторе и деструкторе DerivedLogger добавьте логирование.
Создайте объект DerivedLogger и зафиксируйте порядок вызовов конструкторов и деструкторов.
Цель: Изучить жизненный цикл объектов при наследовании.
8. Анализ кода
Дан код:
Logger createLogger() { return Logger(); } int main() { Logger a; Logger b = a; Logger c = std::move(createLogger()); std::vector<Logger> vec; vec.push_back(Logger()); return 0; }
Предскажите, сколько раз вызываются конструкторы (копирования, перемещения) и деструкторы.
Проверьте свой ответ, добавив логирование в класс Logger.
Цель: Научиться анализировать жизненный цикл объектов в реальном коде.
Лига программистов
2.1K постов11.9K подписчика
Правила сообщества
- Будьте взаимовежливы, аргументируйте критику
- Приветствуются любые посты по тематике программирования
- Если ваш пост содержит ссылки на внешние ресурсы - он должен быть самодостаточным. Вариации на тему "далее читайте в моей телеге" будут удаляться из сообщества