C++ Template Singletons in a dll

跟風遠走 提交于 2021-02-18 05:14:18

问题


In dll A I have a template singleton:

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

private:
  //All constructors are here
};

In Dll B I define a class Logger. Dlls C,D and E use the Logger and it is accessed like this:

Singleton<Logger>::instance();

The problem is that each dll instantiates its own copy of

Singleton<Logger>.

instead of using the same singleton instance. I understand that the solution to this problem is using extern templates. That is dlls C,D and E have to include

extern template class Singleton<Logger>;

and dll B must include:

template class Singleton<Logger>;

This still cause more than one template instance to be created. I tried putting the extern in all the dlls and it still didn't work I tried removing the extern from all the dlls and it still didn't work. Is this not the standard way to implement template singletons? What is the correct way to do this?


回答1:


The trick that works for me is to add __declspec(dllexport) to the singleton's template definition; split the template implementation from the class definition and only include the implementation in the A DLL; and finally, force the template to be instantiated in the A DLL by creating a dummy function that calls Singleton<Logger>::instance().

So in your A DLL's header file, you define the Singleton template like this:

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

Then in your A DLL's cpp file you define the template implementation, and force an instantiation of Singleton<Logger> like this:

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

void instantiate_logger() {
  Singleton<Logger>::instance();
}

With my compiler at least, I don't need to call instantiate_logger from anywhere. Just having it exist forces the code to be generated. So if you dump the A DLL's export table at this point, you should see an entry for Singleton<Logger>::instance().

Now in your C DLL and D DLL, you can include the header file with the template definition for Singleton, but because there is no template implementation, the compiler won't be able to create any code for that template. This means the linker will end up complaining about unresolved externals for Singleton<Logger>::instance(), but you just have to link in the A DLL's export library to fix that.

The bottom line is that the code for Singleton<Logger>::instance() is only ever implemented in DLL A, so you can never have more than one instance.




回答2:


The "correct" way to do this is...not to use a singleton.

If you want all other code to use the same instance of some type, then give that code a reference to that instance - as a parameter to a function or a constructor.

Using a singleton (non-template) would be exactly the same as using a global variable, a practice you should avoid.

Using a template means the compiler decides how to instantiate the code, and how to access the "instance". The problem you're experiencing is a combination of this and using a static in a DLL.

There are many reasons why singletons are bad, including lifetime issues (when, exactly, would it be safe to delete a singleton?), thread-safety issues, global shared access issues and more.

In summary, if you only want one instance of a thing, only create one instance of it, and pass it around to code that needs it.




回答3:


MSDN says that

Win32 DLLs are mapped into the address space of the calling process. By default, each process using a DLL has its own instance of all the DLLs global and static variables. If your DLL needs to share data with other instances of it loaded by other applications, you can use either of the following approaches:

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:


Here's a really sketchy solution that you might be able to build from. Multiple templates will be instantiated but they will all share the same instance objects.

Some additional code would be needed to avoid the memory leak (e.g. replace void * with boost::any of shared_ptr or something).

In 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);

}

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

}

I can't think of a better way right now. The instances have to be stored in a common location.




回答5:


I recommend to combine a refcounted class and an exported api in your Logger class:

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

The code needs some fixing but I try to give you the basic idea.




回答6:


I think your problem in your implementation:

static T _instance;

I assume that static modifier causes compiler to create code in which your T class instances one for each dll. Try different implementations of a singletone. You can try to make static T field in Singletone class. Or maybe Singletone with static pointer inside class should work. I'd recommend you to use second approach, and in your B dll you will specify

Singletone<Logger>::instance = nullptr;

Than on first call for instance() this pointer will be initialized. And I think this will fix your problem.

PS. Don't forget manually handle mutlithreading instancing




回答7:


Make some condition like

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

    return _instance;
}

This will create only single instance and when it got calls for 2nd time it will just return older instance.



来源:https://stackoverflow.com/questions/17614172/c-template-singletons-in-a-dll

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!