Singleton with parameter initialisation

Viewed 230

I'm wondering whats the best way to make a safe singleton that needs to be called with an initialise() with arguments, before it will return a valid object using the getInstance() call. E.g.

#include <memory>
#include <mutex>
#include <atomic>

class MySingleton {
public:
    static void initialise(const std::string& myName) {
        std::lock_guard<std::mutex> lock(MySingleton::m_initMutex);
        if (!MySingleton::m_initialised) {
             m_self = std::shared_ptr<MySingleton>(new MySingleton(myName));
             MySingleton::m_initialised = true;
        }
    }

    static std::shared_ptr<MySingleton> getInstance(){
        if ( MySingleton::m_initialised.load()) {
            return m_self;
        } else {
            return nullptr;
        }
    }

    MySingleton(const MySingleton&) = delete;
    MySingleton& operator=(const MySingleton&) = delete;
private:
    MySingleton(const std::string myName) : m_myName(myName) {}
    
    static std::shared_ptr<MySingleton> m_self;
    static std::mutex m_initMutex;
    static std::atomic<bool> m_initialised;

    std::string m_myName;
};

std::shared_ptr<MySingleton> MySingleton::m_self{nullptr};
std::atomic<bool> MySingleton::m_initialised{false};
std::mutex MySingleton::m_initMutex;

Is there anything dangerous/not thread safe about this? Is there a cleaner/better way to approach this?

2 Answers

Here's a minimal Meyers-like singleton with initialisation.

class Singleton
{
  private:
    int data;
    Singleton(int d) : data(d) {};
    Singleton(const Singleton&) = delete;
    void operator=(const Singleton&) = delete;
  public:
    int getData() { return data; }

    static Singleton& instance(std::function<Singleton()> *init = nullptr) {
        static Singleton s{(*init)()};
        return s;
    }

    static void initialize(int d) {
        std::function<Singleton()> init = [d]() { return Singleton(d); };
        instance(&init);
    }
};

If you call instance without calling initialize first, it will crash on dereferencing a null pointer (of course feel free to insert asserts/checks to catch this in debug mode, or in release mode if you are so inclined).

This requires C++17; for earlier versions of the language, replace delete with default.

According to C++ standard any static variable inside function's body is always protected with atomic bool (to check if it is initialized) and intialization is protected with mutex.

This makes static variable a very handy tool to create simplest yet thread safe solution for implementing Singleton concept. I do this in following 5 lines of code, together with usage of std::optional to pass initial value. Optional's method .value() throws an exception if we forget to pass init value for the first time.

As you see I made Singleton function templated so you can store any type inside and also added second optional UniqueTag param, so that you can create several singleton's out of one type.

To remind, any templated function has its own static variable instance for every instantiation of a template. So all singletons have different static variable inside.

Try it online!

#include <optional>
#include <iostream>

template <typename T, typename UnqiueTag = void>
T const & Singleton(std::optional<T> const & init = std::nullopt) {
    static T val{init.value()};
    return val;
}

int main() {
    struct First {};
    struct Second {};
    std::cout
        << Singleton<int, First>(123) << " "
        << Singleton<int, First>() << " "
        << Singleton<int, Second>(456) << " "
        << Singleton<int, Second>() << " "
        << Singleton<std::string>("Hello!") << " "
        << Singleton<std::string>() << " "
        << std::endl;
}

Output:

123 123 456 456 Hello! Hello!
Related