Reputation: 1921
We are in the process of deprecating ACE libraries in our project which consists of around 120 binaries and in many of binaries we have used ACE_Singleton.Since after deprecating we will not have this class so we are thinking of writing our own generic singleton in our shared library that is common across all these binaries and one of the goals that we want to achieve is if somebody inherit from this class (using CRTP) say Logger and even when Logger constructor is public then also we cannot create two logger object. To illustrate let's assume my singleton class name is GenericSingleton and my client class is logger then following code should throw error :
class Logger:public GenericSingleton<Logger>
{
public:
Logger()
{
}
};
int main()
{
Logger obj;// first instance no issue
Logger obj1; // second instance is problem as it is inherited from singleton
}
So can somebody suggest me how GenericSingleton should be designed so that while creating second object i should get compiletime error ?
In short is there a way that if my derived class doesn't have private constructor,destructor copy constructor etc. then it can be checked at compiletime using static_assert ?
Upvotes: 2
Views: 2720
Reputation: 16499
There's no way for a constructor to know at compile time where or how many times it will be called; constructors are just functions, and functions don't know anything about their contexts. Consider that any static_assert
s will be evaluated when the class is compiled, but this can (and almost certainly will!) happen in an entirely different translation unit from code that actually instantiates the class.
In any case, it seems unlikely that this would be helpful, because you must have some way to access singletons throughout your codebase.
Additionally, it's unclear why you want to permit your singletons to have public constructors. If you want to enforce singleton behavior at compile time for a completely arbitrary class just by adding an inheritance declaration, you're out of luck; arbitrary classes can be, well, arbitrarily constructed.
Since you're transitioning from the ACE singleton, I suggest you use a similar API; note that the ACE singleton documentation recommends making your singleton constructors private.
If, however, you just want some way to force your client to write a constructor that can't (easily) be called improperly, you can do something like this:
template <typename T>
class SingletonBase {
protected: class P { friend class SingletonBase<T>; P() = default; };
public:
SingletonBase(P) {}
static T& Instance() {
static T instance { P {} };
return instance;
}
};
(You will also need to delete
the base class's copy and move constructors. Here is a working example use case. Note that declaring P's constructor =default
does not prevent the singleton class from default-initializing instances of P. )
Now, because the base class constructor takes an argument of type P, the singleton class implementation must pass a P to its parent class constructor. But since the constructor for P is private, the singleton class won't be able to construct an instance of P except by copy or move construction, so its constructor must take an instance of P. But since P itself is protected, only the singleton class and the parent class can actually use it, so effectively the only possible call to the child's constructor must be in the Instance
method.
Note that you do not need to explicitly declare and define the singleton-class's constructor, which would be ugly because of the need to use SingletonBase<Singleton>::P
. You can simply expose the constructor with a using
declaration:
using BASE = SingletonBase<Myclass>;
using BASE::SingletonBase;
Upvotes: 1
Reputation: 69902
My advice would be to separate concerns. There is the concept of a service (such as a logger) and the service may or may not be a singleton. But this is an implementation detail, and therefore a separate concern. The consumer of the service ought to be agnostic of it.
Now, later in the lifecycle of the project, when you realise that singletons were a terrible idea, you can refactor the singleton without having to refactor any code that depends on it.
e.g.:
template<class Impl>
struct implements_singleton
{
using impl_type = Impl;
static impl_type& get_impl()
{
static impl_type _{};
return _;
}
};
struct logger_impl
{
void log_line(std::string const& s)
{
std::clog << s << std::endl;
}
};
struct logger
: private implements_singleton<logger_impl>
{
void log_line(std::string const& s) {
get_impl().log_line(s);
}
};
void do_something(logger& l)
{
l.log_line("c");
}
int main()
{
logger a;
logger b;
a.log_line("a");
b.log_line("b"); // behind the scenes this is the same logger
// but the user need not care
do_something(a);
}
Upvotes: 0
Reputation: 206667
You need to make sure that instances of Logger
cannot be created outside the function that creates the sole instance of Logger
.
Here's a simple implementation.
template <typename T> class GenericSingleton {
public:
static T& instance() {
static T instance_;
return instance_;
}
};
class Logger: public GenericSingleton<Logger>
{
// Make sure that the base class can create the sole instance of
// this class.
friend GenericSingleton<Logger>;
private:
// Makes sure that you cannot create objects of the class
// outside GenericSingleton<Logger>
~Logger() {}
public:
void foo() {}
};
int main()
{
// OK. Call foo() on the only instance of the class.
Logger::instance().foo();
// Not OK.
Logger obj1;
}
Upvotes: 0