Когда уничтожается локальный объект со статическим временем хранения, созданный при первом обращении к функции, и почему это не совпадает с выходом из функции?
Такой объект создаётся при первом прохождении управления через его объявление, а уничтожается при нормальном завершении программы, а не при выходе из функции. Если инициализация завершилась успешно, деструктор объекта будет вызван один раз во время завершения программы.
Локальные объекты со статическим временем хранения позволяют отложить создание ресурсоёмкого объекта до первого использования, сохраняя его до конца процесса. Этот механизм часто применяют для кэширования, единственных экземпляров сервисов и безопасной ленивой инициализации.
RAII переносит освобождение ресурса в деструктор, поэтому длительность жизни такого объекта напрямую определяет длительность удержания ресурса. В отличие от обычного локального объекта, его область видимости ограничена функцией, но время жизни не ограничено выходом из неё.
Если ожидать уничтожения объекта при выходе из функции, можно ошибочно считать ресурс уже освобождённым или попытаться вручную управлять его временем жизни. Обратная ошибка также опасна: объект может удерживать файл, соединение или поток до завершения процесса.
Особый риск возникает при завершении программы: деструктор локального статического объекта может обратиться к другому статическому объекту, который уже уничтожен. Тогда возникает обращение к завершившему время жизни объекту и неопределённое поведение.
Инициализация выполняется при первом достижении объявления. В современном C++ такая инициализация локального статического объекта потокобезопасна: если несколько потоков одновременно впервые вызывают функцию, объект создаётся только один раз, а остальные потоки ожидают завершения инициализации.
Вызов name() не уничтожает value: функция возвращает управление, но объект продолжает существовать. При нормальном завершении программы вызывается его деструктор, если инициализация действительно завершилась успешно.
Если конструктор выбросил исключение, объект не считается созданным; следующий вызов функции может повторить инициализацию. Если программа завершена через std::abort() или _Exit(), обычное уничтожение статических объектов не выполняется. При std::quick_exit() применяются функции, зарегистрированные для быстрого завершения, но обычные деструкторы статических объектов также не вызываются.
Порядок уничтожения определяется порядком завершения инициализации статических объектов: обычно объекты уничтожаются в обратном порядке. Однако зависимости между статическими объектами из разных единиц трансляции нельзя надёжно строить только на порядке их объявлений; это источник проблемы статической инициализации и деинициализации.
Компромисс таков: локальный статический объект удобен для ленивого создания и автоматически освобождает ресурс, но его длительный срок жизни усложняет управление зависимостями при завершении программы. Если ресурс должен освобождаться раньше или порядок остановки важен, лучше использовать явное владение на уровне приложения и передавать зависимости явно.
Сервис журналирования хранится в локальном статическом объекте и владеет файловым дескриптором. Деструктор сервиса записывает финальное сообщение через другой локальный статический объект форматирования, но объект форматирования уже уничтожен из-за порядка завершения.
Вариант с ручным вызовом метода остановки раньше завершения программы даёт контроль, но требует дисциплины: любой путь завершения должен вызвать остановку ровно один раз. Вариант с намеренной утечкой объекта устраняет проблему порядка уничтожения, однако скрывает ошибки освобождения и неприменим, если ресурс нужно корректно закрыть.
Предпочтительное решение — создать сервис на уровне владельца приложения, передать его зависимостям явно и остановить в определённом порядке. Локальный статический объект можно оставить только для действительно независимого и безопасного до завершения процесса ресурса. В результате порядок освобождения становится частью архитектуры, а не побочным эффектом порядка статических деструкторов.
Что произойдёт, если локальный статический объект ни разу не был использован?
Он не будет создан, поэтому его деструктор вызывать не нужно. Это одно из преимуществ ленивой инициализации: ресурс не выделяется, пока функция действительно не понадобится.
Безопасна ли такая инициализация при одновременном первом вызове из нескольких потоков?
Да, начиная с C++11 инициализация локального статического объекта выполняется потокобезопасно. Один поток выполняет конструирование, остальные не получают доступ к частично созданному объекту и ждут завершения инициализации. Это не делает автоматически потокобезопасными последующие операции над самим объектом.
Можно ли безопасно использовать локальный статический объект из деструктора другого статического объекта?
Не всегда. Если используемый объект уже уничтожен, обращение к нему недопустимо. Даже если он ещё жив, порядок завершения может зависеть от того, в каком порядке объекты были впервые инициализированы, поэтому надёжнее не строить критические зависимости между статическими объектами или управлять ими явным владельцем приложения.