Потокобезопасный singleton
Условие
Реализуйте потокобезопасный паттерн Singleton на C++11 или выше.
Класс должен гарантировать:
- Ровно один экземпляр класса создаётся за всё время работы программы
- Потокобезопасность при первом обращении из нескольких потоков
- Ленивую инициализацию (объект создаётся при первом обращении)
Требования
- Используйте возможности C++11 и выше
- Запретите копирование и перемещение объекта
- Метод
getInstance()должен возвращать ссылку на единственный экземпляр
Пример использования
class Logger : public Singleton<Logger> {
public:
void log(const std::string& message);
};
// Использование
Logger::getInstance().log("Application started");
Бонус
Объясните, почему решение через статическую локальную переменную (Scott Meyers Singleton) является потокобезопасным начиная с C++11.
Комментарии (1)
Ответ сгенерирован нейросетью и может содержать ошибки
Решение: Потокобезопасный Singleton на C++11
Лучший подход: Magic Statics (Scott Meyers)
Начиная с C++11 стандарт гарантирует потокобезопасную инициализацию локальных статических переменных. Это самое элегантное и надёжное решение:
template <typename T>
class Singleton {
public:
static T& getInstance() {
static T instance;
return instance;
}
Singleton(const Singleton&) = delete;
Singleton& operator=(const Singleton&) = delete;
Singleton(Singleton&&) = delete;
Singleton& operator=(Singleton&&) = delete;
protected:
Singleton() = default;
~Singleton() = default;
};
Использование
class Logger : public Singleton<Logger> {
public:
void log(const std::string& message) {
std::cout << "[LOG] " << message << std::endl;
}
protected:
friend class Singleton<Logger>;
Logger() { std::cout << "Logger initialized" << std::endl; }
};
// Использование
int main() {
Logger::getInstance().log("Application started");
Logger::getInstance().log("Working...");
return 0;
}
Почему это потокобезопасно в C++11?
Стандарт C++11 5.2.3 (Variable scope) и 3.3.7 (Block scope) явно гарантируют:
- Guard переменная: Компилятор автоматически генерирует скрытую boolean-переменную для проверки инициализации
- Atomicity: Проверка и инициализация статической переменной атомарны на уровне языка
- Синхронизация потоков: ОС предоставляет механизм синхронизации (mutex на Linux/Windows) для защиты инициализации
- Гарантии: Если поток A начал инициализировать, остальные потоки ждут завершения
Детальное объяснение механизма
ДО C++98: Двойная проверка блокировки (Double Checked Locking) была нужна — сложная, подвержена ошибкам.
C++11 и позже: Компилятор генерирует эквивалент:
static T& getInstance() {
static __guard flag = 0; // скрытая переменная
if (!flag) {
__mutex.lock();
if (!flag) {
new (&instance) T(); // placement new
flag = 1;
}
__mutex.unlock();
}
static T instance;
return instance;
}
Гарантии стандарта:
- Инициализация происходит ровно один раз
- Если несколько потоков вызовут getInstance() одновременно, один выполнит инициализацию, остальные дождутся
- Нет race conditions, нет deadlock'ов
Альтернативные подходы
1. Удаляемые конструкторы (как в моём коде) — запрещают копирование:
Singleton(const Singleton&) = delete;
Singleton& operator=(const Singleton&) = delete;
2. Private конструктор (старый стиль):
private:
Singleton() {}
3. Protected конструктор с friend (для наследования) — подходит для шаблонов:
protected:
friend class Singleton<T>;
Singleton() = default;
Тестирование многопоточности
#include <thread>
#include <vector>
int main() {
std::vector<std::thread> threads;
std::set<Logger*> instances;
for (int i = 0; i < 10; ++i) {
threads.emplace_back([&instances]() {
Logger& logger = Logger::getInstance();
instances.insert(&logger);
});
}
for (auto& t : threads) t.join();
assert(instances.size() == 1); // ровно один экземпляр!
return 0;
}
Выводы
✅ Magic Statics — оптимальное решение для C++11+
✅ Встроенная потокобезопасность без явных мьютексов
✅ Ленивая инициализация (объект создаётся при первом обращении)
✅ Запрет копирования/перемещения через = delete
✅ Чистый, читаемый, стандартный код