Назад к вопросам
Middle
93
questionbank

Как вы реализовали бы паттерн 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++ сценариев предпочтителен первый подход.