amigo421
amigo421

Reputation: 2541

Singleton template with variadic constructor class

I'm trying to implement singleton template which could instantiate (from template argument) object of the class by ctor with some set of the parameters. The code below looks ordinary and working.

template <typename T>
class Singleton
{
public:
    template<typename... Ts>
    static T &instance(Ts &&...args)
    {
        static T instance{std::forward<Ts>(args)...};
        return instance;
    }

protected:
    Singleton() = default;
    ~Singleton() = default;

    Singleton(const Singleton &) = delete;
    Singleton &operator=(const Singleton &) = delete;
};

there is an interesting question:

if we are trying to get the instance using different ctors, intuitively expected that is a single instance:

// create with T with some specific parameters
Singleton<MyClass>::instance(1, nullptr, "test").dosomething();
// ....
// use such version for simpler notation 
// or where the paramters are not available 
Singleton<MyClass>::instance().dosomething();

In C++ template terms, obviously the instances are a different due of different instantiations of template instance() function.

if we write "old style" singleton (Meyer's version) based on member pointer to the instance, it solves the problem as the instance will be outside of template class method.

So the question is - is there some technique to keep "reference-style" singleton and keep this outside of "instance()" function to prevent different instances ? or anything else?

Upvotes: 2

Views: 134

Answers (1)

463035818_is_not_an_ai
463035818_is_not_an_ai

Reputation: 122830

You can store the instance in a static member. The advantage of the meyers singleton is that initialization of the instance is thread safe just by how a local static varible works. Though the same can be achieved differently. As also the static requires the implementation to use some sort of synchronization mechanism, I do not expect too much difference in terms of performance when an explicit synchronization mechanism is used. I use std::once here.

#include <memory>
#include <mutex>
#include <iostream>

template <typename T> class Singleton {
public:
    template<typename... Ts> static T& instance(Ts &&...args) {
        std::call_once(once,[&](){
            _instance = std::make_unique<T>(std::forward<Ts>(args)...);
        });
        return *_instance;
    }
protected:
    static std::unique_ptr<T> _instance;
    static std::once_flag once;
    Singleton() = default;
    ~Singleton() = default;
    Singleton(const Singleton &) = delete;
    Singleton &operator=(const Singleton &) = delete;
};

template <typename T> std::unique_ptr<T> Singleton<T>::_instance;
template <typename T> std::once_flag Singleton<T>::once;

struct T {
    int value;
    T(int x) : value(x) { std::cout << "hello world" << std::endl; }   
    T() = default;
};

int main() {
    std::cout << Singleton<T>::instance(42).value;
    std::cout << Singleton<T>::instance().value;
}

Live Demo (note the -pthread, without it will segfault).

std::call_once ensures that only one thread enters the function and only reach the return when _instance is initialized. Effectively it is the same as the static local.

The caveat is that Singleton<T>::instance() only compiles when T has a default constructor, otherwise it not (https://godbolt.org/z/TMdGe16re). I felt that this is beyond the question being asked here and did not include a solution. You can optionally add a instance() overload that does not call the constuctor but throws an exception when _instance is not yet initialized for the case when T cannot be default constructed.

Last and least the usual dislaimer against the singleton pattern: Don't, blablablbla blablabla blablablubb, globals are evil, blablablubb.

Upvotes: 2

Related Questions