Сингл-шаблоны С++ в dll

В dll A у меня есть шаблон singleton:

template <class T>
class Singleton
{
public:
  static T &instance()
  {
    static T _instance;
    return _instance;
  }

private:
  //All constructors are here
};

В Dll B я определяю класс Logger. Dlls C, D и E используют Logger, и к нему обращаются следующим образом:

Singleton<Logger>::instance();

Проблема заключается в том, что каждый dll создает экземпляр

Singleton<Logger>.

вместо того, чтобы использовать один и тот же экземпляр singleton. Я понимаю, что решение этой проблемы - использование шаблонов extern. Это dlls C, D и E должны включать

extern template class Singleton<Logger>;

и dll B должны включать:

template class Singleton<Logger>;

Это все равно вызывает создание более одного экземпляра шаблона. Я попытался поместить extern во все DLL, и он все еще не работает. Я попытался удалить extern из всех DLL, и он все еще не работает. Разве это не стандартный способ реализации шаблонных синглетонов? Каков правильный способ сделать это?

Ответ 1

Трюк, который работает для меня, - это добавить __declspec(dllexport) к определению шаблона singleton; разбить реализацию шаблона из определения класса и включить только реализацию в DLL; и, наконец, принудительно создайте шаблон в библиотеке DL, создав фиктивную функцию, которая вызывает Singleton<Logger>::instance().

Итак, в вашем файле заголовка DLL вы определяете шаблон Singleton следующим образом:

template <class T>
class __declspec(dllexport) Singleton {
public:
  static T &instance();
};

Затем в вашем файле cpp библиотеки DLL вы определяете реализацию шаблона и принудительно создаете экземпляр Singleton<Logger> следующим образом:

template <class T>
T &Singleton<T>::instance() {
  static T _instance;
  return _instance;
};

void instantiate_logger() {
  Singleton<Logger>::instance();
}

С моим компилятором, по крайней мере, мне не нужно вызывать instantiate_logger из любого места. Просто наличие этого параметра заставляет код генерировать. Поэтому, если вы выгрузите таблицу экспорта DLL в этот момент, вы должны увидеть запись для Singleton<Logger>::instance().

Теперь в вашей DLL DLL и D DLL вы можете включить заголовочный файл с определением шаблона для Singleton, но поскольку реализация шаблона отсутствует, компилятор не сможет создать код для этого шаблона. Это означает, что компоновщик будет жаловаться на нерешенные внешние для Singleton<Logger>::instance(), но вам просто нужно установить ссылку в библиотеке экспорта DLL, чтобы исправить это.

Суть в том, что код для Singleton<Logger>::instance() реализуется только в DLL A, поэтому у вас никогда не может быть более одного экземпляра.

Ответ 2

"Правильный" способ сделать это - не использовать синглтон.

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

Использование singleton (non-template) будет точно таким же, как с использованием глобальной переменной, что вам следует избегать.

Использование шаблона означает, что компилятор решает, как создать экземпляр кода и как получить доступ к "экземпляру". Проблема, с которой вы столкнулись, - это комбинация этого и использование статики в DLL.

Есть много причин, почему синглтоны плохие, в том числе проблемы с жизненным циклом (когда, точно, было бы безопасно удалять одноэлемент?), проблемы безопасности потоков, проблемы глобального доступа и многое другое.

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

Ответ 3

MSDN говорит, что

DLL Win32 отображается в адресное пространство вызывающего процесса. По умолчанию каждый процесс, использующий DLL, имеет свой собственный экземпляр всех DLL - глобальные и статические переменные. Если вашей DLL необходимо обмениваться данными с другие экземпляры, загруженные другими приложениями, вы можете использовать либо из следующих подходов:

Create named data sections using the data_seg pragma.

Use memory mapped files. See the Win32 documentation about memory mapped files.

http://msdn.microsoft.com/en-us/library/h90dkhs0%28v=vs.80%29.aspx

Ответ 4

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

Для предотвращения утечки памяти потребуется дополнительный код (например, замените void * на boost:: любой из shared_ptr или что-то еще).

В singleton.h

#if defined(DLL_EXPORTS)
    #define DLL_API __declspec(dllexport)
#else
    #define DLL_API __declspec(dllimport)
#endif

template <class T>
class Singleton
{
public:
  static T &instance()
  {
      T *instance = reinterpret_cast<T *>(details::getInstance(typeid(T)));
      if (instance == NULL)
      {
          instance = new T();
          details::setInstance(typeid(T), instance);
      }

      return *instance;
  }
};

namespace details
{

DLL_API void setInstance(const type_info &type, void *singleton);
DLL_API void *getInstance(const type_info &type);

}

В singleton.cpp.

#include <map>
#include <string>

namespace details
{

namespace
{

std::map<std::string, void *> singletons;

}

void setInstance(const type_info &type, void *singleton)
{
    singletons[type.name()] = singleton;
}

void *getInstance(const type_info &type)
{
    std::map<std::string, void *>::const_iterator iter = singletons.find(type.name());
    if (iter == singletons.end())
        return NULL;

    return iter->second;
}

}

Я не могу сейчас думать о лучшем пути. Экземпляры должны храниться в общем месте.

Ответ 5

Я рекомендую объединить refcounted класс и экспортированный api в вашем классе Logger:

class Logger
{
public:
  Logger()
    {
    nRefCount = 1;
    return;
    };

  ~Logger()
    {
    lpPtr = NULL;
    return;
    };

  VOID AddRef()
    {
    InterLockedIncrement(&nRefCount);
    return;
    };

  VOID Release()
    {
    if (InterLockedDecrement(&nRefCount) == 0)
      delete this;
    return;
    };

  static Logger* Get()
    {
    if (lpPtr == NULL)
    {
      //singleton creation lock should be here
      lpPtr = new Logger();
    }
    return lpPtr;
    };

private:
  LONG volatile nRefCount;
  static Logger *lpPtr = NULL;
};

__declspec(dllexport) Logger* GetLogger()
  {
  return Logger::Get();
  };

Код нуждается в некоторой фиксации, но я пытаюсь дать вам основную идею.

Ответ 6

Я думаю, что ваша проблема в вашей реализации:

static T _instance;

Я предполагаю, что статический модификатор заставляет компилятор создавать код, в котором ваш экземпляр класса T один для каждой dll. Попробуйте различные реализации синглтона. Вы можете попытаться сделать статическое поле T в классе Singletone. Или, может быть, работает Singletone со статическим указателем внутри класса. Я бы рекомендовал вам использовать второй подход, и в вашей B dll вы укажете

Singletone<Logger>::instance = nullptr;

Вместо первого вызова для экземпляра() этот указатель будет инициализирован. И я думаю, что это решит вашу проблему.

PS. Не забывайте ручную ручную проверку mutlithreading

Ответ 7

Сделайте некоторое условие вроде

instance()
{
    if ( _instance == NULL ) {
    _instance = new Singleton();
    }

    return _instance;
}

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