SingletonHolder: Optionally create an instance with a non-default-constructor?

I have a class which should be a singleton. It has a default constructor but also an alternative constructor which I want to invoke at startup under certain conditions. Is there any clean way to create an alternative instance of my singleton through a different constructor?

Would it make sense to add a member function like

SingletonHolder::setNewInstance (std::unique_ptr<Type>&& newInstance)

so that one could write

auto customInstance = std::make_unique<MyClass> (some args...);
MySingleton::setNewInstance (customInstance);

Thought a bit about it and came to the conclusion that it might be smarter if the custom constructor based instance would be created directly and not outside and then passed over to the singleton. I modified my local JUCE copy, adding the following to juce_Singleton.h

/** Creates an instance, invoking a non-default constructor. If an instance already exists, it will be deleted */
    template <typename... Args>
    Type* createInstance (Args&&... args)
    {
        typename MutexType::ScopedLockType sl (*this);

        if (instance != nullptr)
            delete instance;

        auto once = onlyCreateOncePerRun; // (local copy avoids VS compiler warning about this being constant)

        if (once)
        {
            static bool createdOnceAlready = false;

            if (createdOnceAlready)
            {
                // This means that the doNotRecreateAfterDeletion flag was set
                // and you tried to create the singleton more than once.
                jassertfalse;
                return nullptr;
            }

            createdOnceAlready = true;
        }

        static bool alreadyInside = false;

        if (alreadyInside)
        {
            // This means that your object's constructor has done something which has
            // ended up causing a recursive loop of singleton creation..
            jassertfalse;
        }
        else
        {
            alreadyInside = true;
            instance = new Type (std::forward<Args>(args)...);
            alreadyInside = false;
        }

        return instance;
    }
#define JUCE_DECLARE_SINGLETON(Classname, doNotRecreateAfterDeletion) \
\
    static juce::SingletonHolder<Classname, juce::CriticalSection, doNotRecreateAfterDeletion> singletonHolder; \
    friend decltype (singletonHolder); \
\
    static Classname* JUCE_CALLTYPE getInstance()                           { return singletonHolder.get(); } \
    static Classname* JUCE_CALLTYPE getInstanceWithoutCreating() noexcept   { return singletonHolder.instance; } \
    template <typename... Args> \
    static Classname* JUCE_CALLTYPE createInstance (Args&&... args)         { return singletonHolder.createInstance (std::forward<Args>(args)...); } \
    static void JUCE_CALLTYPE deleteInstance() noexcept                     { singletonHolder.deleteInstance(); } \
    void clearSingletonInstance() noexcept                                  { singletonHolder.clear (this); }

Works fine for me so far. Any thoughts from the JUCE team on adding such a feature?

What you’re describing isn’t really a singleton, it’s just a global variable which gets created at a specific point in your program’s start up.

The point of a singleton is that it gets created lazily by the first caller which needs it, so you don’t have to worry about the order of dependencies in a large codebase. If you impose the rule that it needs to be created at a specific point with specific properties, then that freedom over where it gets constructed disappears, and so does the point of using the singleton pattern for it.

3 Likes

Ok, fair point, @jules, thanks for the feedback that lets me re-think my design.

My use case is an instance that just can be lazily constructed in 99% of the use cases but should be constructed differently if some specific hardware architecture is detected. But maybe I’ll move this logic into the class default constructor instead of invoking different constructors in the application startup routine.

Furthermore after testing my solution posted a bit I realized that the createdOnceAlready check of course does not work in my code if an instance gets created once through the default constructor and once through a custom constructor. :man_facepalming: