Мой синглтон можно вызвать несколько раз - программирование

Мой синглтон можно вызвать несколько раз

Я реализовал синглтон на основе c++ 11. Однако в некоторых случаях конструктор может вызываться несколько раз.

Класс будет скомпилирован в статический lib и использован другим таким образом lib (более одного, таким образом lib). И система является многопоточной системой (работает на уровне Android HAL)

///Файл .h:

class Logger
{
public:

    /// Return the singleton instance of Logger
    static Logger& GetInstance() {
        static Logger s_loggerSingleton;
        return s_loggerSingleton;
    }

private:

    /// Constructor
    Logger();
    /// Destructor
    ~Logger();
}

///.cpp файл

Logger::Logger()
{
   ALOGE("OfflineLogger create");
}

Logger::~Logger()
{

}

Он должен быть создан один раз, например:

03-21 01:52:20.785   728  4522 E         : OfflineLogger create

Однако я вижу, что он был создан не раз

03-21 01:52:20.785   728  4522 E         : OfflineLogger create
03-21 01:52:20.863   728  2274 E         : OfflineLogger create
03-21 01:52:20.977   728  2273 E         : OfflineLogger create
03-21 01:52:26.370   728  4522 E         : OfflineLogger create

Вопросы:

  1. Что-то не так с моим синглтон-дизайном? Это потокобезопасная проблема?

  2. Похоже, мой синглтон отлично работает в одной области, но каждая такая библиотека, которая включает в себя мой синглтон, создаст свой собственный синглтон, так что мой синглтон больше не будет "быть синглтоном". Является ли проблема, вызванная каждой динамической связью с новым, так что "статическая переменная" становится "локальной статической"? Является ли это возможным? Если так, как это исправить?

4b9b3361

Ответ 1

  1. Что-то не так с моим синглтон-дизайном? Это потокобезопасная проблема?

Нет. Стандарт инициализирует локальные static переменные функции как гарантирующие многопоточность.

  1. Похоже, мой синглтон отлично работает в одной области, но каждая такая библиотека, которая включает в себя мой синглтон, создаст свой собственный синглтон, так что мой синглтон больше не будет "быть синглтоном". Является ли проблема, вызванная каждой динамической связью с новым, так что "staic veriable" становится "local static"? Является ли это возможным? Если да то как исправить

Это правильный вывод.

Вместо того, чтобы создавать статическую библиотеку, которая содержит реализацию синглтона, сделайте ее динамической библиотекой.

Ответ 2

Синглтоны сложны, особенно с общими библиотеками.

Каждая из ваших общих библиотек имеет независимую копию не общей библиотеки. Без особой осторожности у каждого будет копия синглтона.

Чтобы иметь нетривиальные синглтоны, мне нужно было

  1. Создайте чрезвычайно низкоуровневую библиотеку для помощи синглетам - назовите ее LibSingleton

  2. Создайте шаблон синглтона, который знает тип синглтона. Он использует магическую статику для отправки запроса в LibSingleton с размером, typeid(T).name() и typeid(T).name() типом кода построения и уничтожения. LibSingleton возвращает объект RAII для подсчета ссылок.

  3. LibSingleton использует общий мьютекс для возврата ранее созданного объекта, который соответствует имени/размеру или создает его. Если он создает объект, он хранит код уничтожения.

  4. Когда последний дескриптор с подсчетом ссылок на данные LibSingleton исчезает, LibSingleton запускает код уничтожения и очищает память в своей неупорядоченной карте.

Это позволяет использовать действительно простые синглтоны практически везде.

template<class T>
class singleton {
public:
  static T& Instance() {
    static auto smart_ptr = LibSingleton::RequestInstance(
      typeid(T).name(),
      sizeof(T),
      [](void* ptr){ return ::new( ptr ) T{}; },
      [](void* ptr){ static_cast<T*>(ptr)->~T(); }
    );
    if (!smart_ptr)
      exit(-1); // or throw something
    return *static_cast<T*>(smart_ptr.get());
  }
protected:
  singleton() = default;
  ~singleton() = default;
private:
  singleton(singleton&&) = delete;
  singleton& operator=(singleton&&) = delete;
};

Использование выглядит так:

struct Logger : LibSingleton::singleton<Logger> {
  friend class LibSingleton::singleton<Logger>;
  void do_log( char const* sting ) {}
private:
  Logger() { /* ... */ }
};

Ответ 3

Сделайте это глобальной переменной в исходном файле. Таким образом, у вас будет только одно определение, даже со статической связью:

Что-то не так с моим синглтон-дизайном? Это потокобезопасная проблема?

"Вид" для первого, "Нет" для последнего

Похоже, мой синглтон отлично работает в одной области, но каждая такая библиотека, которая включает в себя мой синглтон, создаст свой собственный синглтон, так что мой синглтон больше не будет "быть синглтоном". Является ли проблема, вызванная каждой динамической связью с новым, так что "staic veriable" становится "local static"? Является ли это возможным? Если да то как исправить

Если ваш вызов GetInstance действительно встроенный, это дает компилятору свободный проход для создания нескольких определений. Если вы объявите это в исходном файле, это все равно вызовет проблемы, если вы несколько раз будете ссылаться на него как на статическую библиотеку.

Этот код должен работать

// Logger.cpp

Logger*    gLoggerInstance     = nullptr; // NOT STATIC
std::mutex gLoggerInstanceLock;  // NOT STATIC, only if you need thread-safety

Logger& Logger::GetInstance() {
    std::lock_guard(loggerInstanceLock); // only if you need thread-safety
    if(!loggerInstance) loggerInstance = new Logger();
    return *loggerInstance;
}

Редактировать: Только не работает со статическими библиотеками, если вы связываете статические библиотеки в несколько библиотек DLL, которые затем связываете с исполняемым файлом:

A.lib
|     \
X.dll  Y.dll
|     /
G.exe

Ответ 4

Вот идея: вместо того, чтобы использовать одиночный код, который является сложной проблемой в вашей среде и, как известно, проблематичным для тестирования и обслуживания, просто разработайте свой код, чтобы создать только один из рассматриваемых объектов.

Ответ 5

статическая переменная должна быть перемещена в файл .cpp.

Простой способ - сохранить только объявление getInstance() в .h и переместить реализацию в файл .cpp.

Ответ 6

Может быть, что ваш файл заголовка определяется несколько раз (это в случае, если несколько файлов, including этот файл заголовка. Попробуйте добавить караул вокруг заголовка файла, чтобы предотвратить его переопределены, если он уже был определен один раз.

В зависимости от вашего компилятора C++, вы можете просто добавить #pragma once в качестве первой строки в вашем файле, например

#pragma once
class Logger
{
    public:

    /// Return the singleton instance of Logger
    static Logger& GetInstance() {
    static Logger s_loggerSingleton;
    return s_loggerSingleton;
}

private:

    /// Constructor
    Logger();
    /// Destructor
    ~Logger();
}

Предполагаемый эффект - это эффект самой общей альтернативы, которая заключается в добавлении макроопределения, подобного этому

#ifndef LOGGER_H
#define LOGGER_H
class Logger
{
    public:

    /// Return the singleton instance of Logger
    static Logger& GetInstance() {
    static Logger s_loggerSingleton;
    return s_loggerSingleton;
}

private:

    /// Constructor
    Logger();
    /// Destructor
    ~Logger();
}
#endif LOGGER_H