一个DLL中的C ++模板单例

时间:2013-07-12 11:42:13

标签: c++ templates singleton

在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并且它仍然无法工作。这不是实现模板单例的标准方法吗?这样做的正确方法是什么?

7 个答案:

答案 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)

MSDN说

  

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;
}

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