Как вы реализовали бы паттерн Singleton в своем коде?
Sobes Copilot
Получайте ответы в реальном времени
Ответ от нейросети
sobes.tech AI
Многопоточный Singleton с ленивой инициализацией:
#include <iostream>
#include <mutex>
class Singleton {
public:
// Метод для получения экземпляра Singleton
static Singleton& getInstance() {
// Используем статический локальный объект для гарантированной ленивой инициализации
// и потокобезопасности (начиная с C++11)
static Singleton instance;
return instance;
}
// Запрещаем копирование и присваивание
Singleton(const Singleton&) = delete;
Singleton& operator=(const Singleton&) = delete;
private:
// Приватный конструктор для предотвращения создания экземпляров вне класса
Singleton() {
// Инициализация экземпляра (если требуется)
std::cout << "Singleton instance created.\n";
}
// Приватный деструктор (если требуется освобождение ресурсов),
// управляется временем жизни статического локального объекта
~Singleton() {
std::cout << "Singleton instance destroyed.\n";
}
};
// Пример использования
int main() {
// Получаем доступ к единственному экземпляру
Singleton& s1 = Singleton::getInstance();
Singleton& s2 = Singleton::getInstance();
// Проверяем, что s1 и s2 ссылаются на один и тот же объект
if (&s1 == &s2) {
std::cout << "s1 and s2 are the same instance.\n";
}
return 0;
}
Этот подход использует "волшебную" статическую переменную (Meyers' Singleton), которая гарантированно инициализируется при первом вызове функции getInstance() и потокобезопасна начиная с C++11. Это устраняет необходимость в явной синхронизации с мьютексами для инициализации.
Альтернативный подход (для старых стандартов C++ или явного контроля инициализации) с использованием мьютекса:
#include <iostream>
#include <mutex>
#include <atomic> // Для обеспечения правильной видимости изменений
class SingletonAtomic {
public:
static SingletonAtomic* getInstance() {
// Двойная блокировка проверки для эффективности
if (!instance.load(std::memory_order_acquire)) {
std::lock_guard<std::mutex> lock(mutex);
if (!instance.load(std::memory_order_relaxed)) {
instance.store(new SingletonAtomic(), std::memory_order_release);
}
}
return instance.load(std::memory_order_acquire);
}
// Не забыть про освобождение памяти!
static void destroyInstance() {
std::lock_guard<std::mutex> lock(mutex);
delete instance.load(std::memory_order_relaxed);
instance.store(nullptr, std::memory_order_relaxed);
}
SingletonAtomic(const SingletonAtomic&) = delete;
SingletonAtomic& operator=(const SingletonAtomic&) = delete;
private:
SingletonAtomic() {
std::cout << "SingletonAtomic instance created.\n";
}
~SingletonAtomic() {
std::cout << "SingletonAtomic instance destroyed.\n";
}
// Используем std::atomic для обеспечения правильной видимости указателя
static std::atomic<SingletonAtomic*> instance;
static std::mutex mutex;
};
// Инициализация статических членов класса
std::atomic<SingletonAtomic*> SingletonAtomic::instance(nullptr);
std::mutex SingletonAtomic::mutex;
/*
// Пример использования:
int main() {
SingletonAtomic* s1 = SingletonAtomic::getInstance();
SingletonAtomic* s2 = SingletonAtomic::getInstance();
if (s1 == s2) {
std::cout << "s1 and s2 are the same instance.\n";
}
// Важно освободить память при завершении программы!
SingletonAtomic::destroyInstance();
return 0;
}
*/
Этот вариант требует явного управления временем жизни экземпляра (destroyInstance()) и более сложен с точки зрения обеспечения корректности в многопоточной среде (необходима двойная блокировка проверки с правильными моделями памяти). В большинстве современных C++ сценариев предпочтителен первый подход.