Reputation: 2541
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
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