在dll A中我有一个模板单例:
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>.
而不是使用相同的单例实例。我知道这个问题的解决方案是使用extern模板。这就是d,C,D和E必须包括
extern template class Singleton<Logger>;
和dll B必须包括:
template class Singleton<Logger>;
这仍然会导致创建多个模板实例。我尝试将extern放在所有的dll中但它仍然无法工作我尝试从所有dll中移除extern并且它仍然无法工作。这不是实现模板单例的标准方法吗?这样做的正确方法是什么?
答案 0 :(得分:7)
“正确”的方法是......不要使用单身人士。
如果您希望所有其他代码使用某种类型的相同实例,则为该代码提供对该实例的引用 - 作为函数或构造函数的参数。
使用单例(非模板)与使用全局变量完全相同,这是您应该避免的做法。
使用模板意味着编译器决定如何实例化代码,以及如何访问“实例”。您遇到的问题是这种情况的组合以及在DLL中使用静态。
单身人士不好的原因有很多,包括终身问题(确切地说,删除单身人员是否安全?),线程安全问题,全球共享访问问题等等。
总之,如果你只想要一个事物的一个实例,只需创建它的一个实例,并将其传递给需要它的代码。
答案 1 :(得分:7)
对我有用的技巧是将__declspec(dllexport)
添加到单例的模板定义中;从类定义中拆分模板实现,只包含A DLL中的实现;最后,通过创建一个调用Singleton<Logger>::instance()
的虚函数来强制模板在A DLL中实例化。
因此,在A DLL的头文件中,您可以像这样定义 Singleton 模板:
template <class T>
class __declspec(dllexport) Singleton {
public:
static T &instance();
};
然后在你的A DLL的cpp文件中定义模板实现,并像这样强制Singleton<Logger>
实例化:
template <class T>
T &Singleton<T>::instance() {
static T _instance;
return _instance;
};
void instantiate_logger() {
Singleton<Logger>::instance();
}
至少使用我的编译器,我不需要从任何地方调用instantiate_logger
。只是存在它会强制生成代码。因此,如果此时转储A DLL的导出表,您应该会看到Singleton<Logger>::instance()
的条目。
现在在您的C DLL和D DLL中,您可以包含带有Singleton
模板定义的头文件,但由于没有模板实现,编译器将无法为此创建任何代码模板。这意味着链接器最终会抱怨Singleton<Logger>::instance()
的未解析的外部,但您只需链接A DLL的导出库来修复它。
底线是Singleton<Logger>::instance()
的代码只在DLL A中实现,因此您永远不会有多个实例。
答案 2 :(得分:5)
Win32 DLL映射到调用进程的地址空间。 默认情况下,使用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
答案 3 :(得分:4)
这是一个非常粗略的解决方案,您可以从中构建。将实例化多个模板,但它们将共享相同的实例对象。
需要一些额外的代码来避免内存泄漏(例如用boost :: any shared_ptr替换void *)。
在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;
}
}
我现在想不出更好的方法。实例必须存储在一个公共位置。
答案 4 :(得分:3)
我建议在您的Logger类中组合一个refcounted类和一个导出的api:
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();
};
代码需要一些修复,但我试着给你一个基本的想法。
答案 5 :(得分:1)
我认为您的实施中存在问题:
static T _instance;
我假设static modifier导致编译器创建代码,其中T类为每个dll实例一个。 尝试单线程的不同实现。 您可以尝试在Singletone类中创建静态T字段。或者也许Singletone在类中有静态指针应该可以工作。我建议你使用第二种方法,在你的B dll中你将指定
Singletone<Logger>::instance = nullptr;
比第一次调用instance()时,将初始化此指针。我认为这将解决您的问题。
PS。不要忘记手动处理多线程实例化
答案 6 :(得分:-2)
制定一些像
这样的条件instance()
{
if ( _instance == NULL ) {
_instance = new Singleton();
}
return _instance;
}
这将只创建单个实例,当它第二次调用时,它将只返回旧实例。