dll中的C++模板Singleton

C++ Template Singletons in a dll

本文关键字:Singleton 模板 C++ 中的 dll      更新时间:2023-10-16

在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实例。我知道这个问题的解决方案是使用外部模板。也就是说,dll C、D和E必须包括

extern template class Singleton<Logger>;

和dll B必须包括:

template class Singleton<Logger>;

这仍然会导致创建多个模板实例。我试着把extern放在所有dll中,但仍然不起作用。我试着从所有dll中删除extern,但仍然没有起作用。这不是实现模板单件的标准方法吗?正确的方法是什么?

对我有用的技巧是将__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()的未解析外部,但您只需要链接到DLL的导出库中即可解决此问题。

最重要的是,Singleton<Logger>::instance()的代码只在DLL A中实现过,所以永远不能有多个实例。

实现这一点的"正确"方法是……不使用单例。

如果您希望所有其他代码都使用某个类型的同一实例,那么请为该代码提供对该实例的引用,作为函数或构造函数的参数。

使用singleton(非模板)与使用全局变量完全相同,应该避免这种做法。

使用模板意味着编译器决定如何实例化代码,以及如何访问"实例"。你遇到的问题是这一点和在DLL中使用静态的结合。

单身汉不好的原因有很多,包括生存期问题(确切地说,删除单身汉什么时候安全?)、线程安全问题、全局共享访问问题等等。

总之,如果你只想要一个事物的一个实例,只需要创建一个实例并将其传递给需要它的代码

Win32 DLL被映射到调用进程的地址空间中。默认情况下,每个使用DLL的进程都有自己的所有DLL全局变量和静态变量。如果您的DLL需要与共享数据其他应用程序加载的其他实例,可以使用以下方法之一:

使用data_seg杂注创建命名数据节。

使用内存映射文件。请参阅有关内存映射文件的Win32文档。

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

这里有一个非常粗略的解决方案,您可能可以从中构建。多个模板将被实例化,但它们都将共享相同的实例对象。

需要一些额外的代码来避免内存泄漏(例如,将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;
}
}

我现在想不出更好的办法了。实例必须存储在一个公共位置。

我建议在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();
  };

代码需要一些修改,但我试着给你一个基本的想法。

我认为您在实现中的问题是:

static T _instance;

我假设static修饰符会导致编译器创建代码,其中T类为每个dll实例一个。尝试使用singletone的不同实现。您可以尝试在Singletone类中创建静态T字段。或者,类中带有静态指针的Singletone应该可以工作。我建议你使用第二种方法,在你的B dll中你会指定

Singletone<Logger>::instance = nullptr;

在第一次调用instance()时,此指针将被初始化。我认为这会解决你的问题。

PS。不要忘记手动处理mutlithreading实例化

创建一些类似的条件

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

这将只创建一个实例,当它第二次被调用时,它将只返回旧实例。