11

在 dll AI 中有一个模板单例:

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

private:
  //All constructors are here
};

在 Dll BI 中定义一个类 Logger。Dlls C、D 和 E 使用 Logger,它的访问方式如下:

Singleton<Logger>::instance();

问题是每个 dll 都实例化了自己的

Singleton<Logger>.

而不是使用相同的单例实例。我知道这个问题的解决方案是使用外部模板。即 dll C、D 和 E 必须包括

extern template class Singleton<Logger>;

和 dll B 必须包括:

template class Singleton<Logger>;

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

4

7 回答 7

7

做到这一点的“正确”方法是......不要使用单例。

如果您希望所有其他代码使用某种类型的相同实例,则为该代码提供对该实例的引用 - 作为函数或构造函数的参数。

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

使用模板意味着编译器决定如何实例化代码,以及如何访问“实例”。您遇到的问题是这个和在 DLL 中使用静态的组合。

单例不好的原因有很多,包括生命周期问题(确切地说,何时删除单例是安全的?)、线程安全问题、全局共享访问问题等等。

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

于 2013-07-15T13:58:04.417 回答
7

对我有用的技巧是添加__declspec(dllexport)到单例的模板定义中;将模板实现从类定义中分离出来,只将实现包含在 A DLL 中;最后,通过创建一个调用Singleton<Logger>::instance().

因此,在您的 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().

底线是代码Singleton<Logger>::instance()只在 DLL A 中实现,因此您永远不能拥有多个实例。

于 2013-07-19T16:09:57.270 回答
5

MSDN 说

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

使用 data_seg pragma 创建命名数据部分。

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

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

于 2013-07-12T12:42:20.763 回答
4

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

需要一些额外的代码来避免内存泄漏(例如,将 void * 替换为 boost::any of shared_ptr 或其他东西)。

在单例.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;
}

}

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

于 2013-07-15T14:31:08.567 回答
3

我建议在您的 Logger 类中结合使用引用计数的类和导出的 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();
  };

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

于 2013-07-14T23:35:01.150 回答
1

我认为您在实施中的问题:

static T _instance;

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

Singletone<Logger>::instance = nullptr;

与第一次调用 instance() 相比,此指针将被初始化。我认为这将解决您的问题。

PS。不要忘记手动处理多线程实例化

于 2013-07-19T14:24:06.670 回答
-2

做一些条件,比如

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

    return _instance;
}

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

于 2013-07-12T11:51:29.797 回答