Search code examples
c++dllsingletonshared

Multiple instances of Singleton in a c++ shared library on Windows


If I access a singleton in an executable that is linked a shared library and then access the same singleton within the shared library, two instances are created.

Is this not allowed or am I doing something wrong?

Here is some code to illustrate. I create a shared library containing Singleton.h, Manager.h, Window.h, Window.cpp, and DeclSpec.h

Singleton template class:

#ifndef SINGLETON_H
#define SINGLETON_H

#include <memory>
#include <iostream>

template <typename T>
class Singleton
{
private:
    static std::unique_ptr<T> s_instance;

public:
    static T &instance()
    {
        if (!s_instance) {
            std::cout << "Creating..." << std::endl;
            s_instance = std::make_unique<T>();
        }

        return *s_instance;
    }
};

template <typename T>
std::unique_ptr<T> Singleton<T>::s_instance;

#endif //SINGLETON_H

I create a manager class that is a singleton:

#ifndef MANAGER_H
#define MANAGER_H

#include "Singleton.h"

class Manager : public Singleton<Manager>
{
};

#endif //MANAGER_H

Then I have a window class within the same library that uses the manager.

Here is the h (DECLSPEC is defined in DeclSpec.h and handles library export/import):

#ifndef WINDOW_H
#define WINDOW_H

#include "DeclSpec.h"

class DECLSPEC Window
{

public:
    Window();

};

#endif //WINDOW_H

Here is the cpp:

#include "Manager.h"
#include "Window.h"

Window::Window()
{
    Manager &m = Manager::instance();
}

Finally, I create an executable that is linked to the above shared library with a simple main.cpp:

#include "Manager.h"
#include "Window.h"

int main(void)
{
    Manager &m = Manager::instance();
    Window w;

    return 0;
}

The output:

Creating...
Creating...

The singleton is created twice.

Any pointers?


Solution

  • Just like with any other class, you need to tell the compiler to either export or import the template class specialization. Here is how to properly declare your template specialization as import or export.

    #ifndef MANAGER_H
    #define MANAGER_H
    
    #include "Singleton.h"
    
    // <BEGIN modifications>
    // forward declare the Manager class
    class Manager;
    // delcare your specialization as import or export
    // NOTE: this only works on win32.
    // For other platforms you would use the 'extern' keyword, and it does not go in the same place as DECLSPEC
    template class DECLSPEC Singleton<Manager>;
    // <END modifications>
    
    class Manager : public Singleton<Manager>
    {
    };
    
    #endif //MANAGER_H
    

    The goal here is to tag the specific specialization of Singleton with DECLSPEC. We dont want to tag the template class itself because you may want to have singletons of other types that dont live in your main executable.

    The microsoft compiler will not implement the templates tagged with a declspec. Other compilers (clang, gcc) use the 'extern' keyword for this purpose. So you are responsible for explicitly instantiating the template in one of your cpp files. If you forget to do this, you will get linker errors just like you had made a class member function and forgot to implement it.

    Conversely, if you forget to reference Singleton somewhere in your code without the DECLSPEC (like if you forward declare the class), you will get 'multiple symbols defined' linker errors.

    So add the following to your Manager.cpp to force the compiler to fully instantiate the template specialization of your Singleton:

    template class Singleton<Manager>;