Сингл-шаблоны С++ в 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;
}
Это создаст только один экземпляр, и когда он получит вызовы во второй раз, он просто вернет старый экземпляр.