To singleton or not to singleton, that is the question....
-
It's 2024. I use C++ 17. Once and for all I want to put to bed the "best" or "preferred" way to have a set of "global" functions. By which I mean can be called from any module, without passing something into code there to allow it to access such.
The pattern I have picked and written is the following with singleton:
#ifndef UTILS_H #define UTILS_H #include <algorithm> #include <random> class RandomNumber { public: static RandomNumber& instance() { static RandomNumber singleton; return singleton; } static std::mt19937 &random_generator(); template<typename _RAIter> void random_shuffle(_RAIter itBegin, _RAIter itEnd) { std::shuffle(itBegin, itEnd, random_generator()); } int random_int(int range) const; }; #endif // UTILS_H
and
#include "utils.h" /*static*/ std::mt19937 &RandomNumber::random_generator() { static std::random_device rd; static std::mt19937 gen(rd()); return gen; } int RandomNumber::random_int(int range) const { std::uniform_int_distribution<> distr(0, range); return distr(random_generator()); }
So that any outside world module can
#include "utils.h"
and then call likeRandomNumber::instance().random_shuffle(....) RandomNumber::instance().random_int(10)
Can we confirm this is still regarded as the "best" pattern for this kind of thing as of now? If so I will mark this as solution and can relax happily....
(Btw, I am aware there is debate about concurrency/threads for this pattern. That is not my concern here.)
-
@jsulm said in To singleton or not to singleton, that is the question....:
avoid singletons!
Sigh, that's what I mean, I still find plenty out there advocating using singletons and now you say avoid, that is what I feared when I thought I had got to grips with it... ;-)
Why do you need a class here?
A namespace would be enough.
Because I don't know how to write/use namespaces in my own code! Can you show me how this could/would be written (and called) the way you suggest, please?
-
#ifndef UTILS_H #define UTILS_H #include <algorithm> #include <random> namespace RandomNumber { std::mt19937 &random_generator(); template<typename _RAIter> void random_shuffle(_RAIter itBegin, _RAIter itEnd) { std::shuffle(itBegin, itEnd, random_generator()); } int random_int(int range) const; } #endif // UTILS_H ... RandomNumber::random_shuffle(....); RandomNumber::random_int(10);
-
@jsulm
Oh! Well if I had known it was that simple I would have tried it! :) I have changed my code to your namespace way (as you wrote, had to remove thestatic
fromrandom_generator()
function) and it all works fine :)Let's go through a few things then:
(avoid singletons!)
Well, your way is just "global" functions with a
namespace
tagged on to make it look a bit fancier :) We are also told to avoid global functions....!I may have been thinking about if I needed to do some initialization stuff to use it. With singleton/class you still have a constructor where I could do that (not in my example, but maybe another), can't do that with namespace.
On the same sort of level, if I had needed some member variables I could have them; with namespace they would have to be global variables. And that would also lead to problems with when any initialization of them occurred, where burying them in an instance means I can be sure they are not performed till the instance is first called for.
Also with a class I can declare variables or methods
public
orprivate
.namespace
does not have that (all is public, you have to wrap inside a second, nested, anonymous namespace to achieveprivate
).So would it be that this example works fine without class/singleton but another case might not?
When did C++ have namespaces? If it's all this simple why the endless debates and examples of using singletons for this sort of thing?
-
@jsulm
P.S. Additional implementation question/style withnamespace
.I rewrote my code your way, leaving the function body definitions inside the
.cpp
viaint RandomNumber::random_int() { } ...
i.e. with the
RandomNumber::
prefix on everything as it was when it was aclass
. I see that you can write in the.cpp
:namespace RandomNumber { int random_int() { } ... }
which you cannot do for
class
. Which to prefer? I'm actually thinking of sticking with the prefix as if it were aclass
? -
@JonB said in To singleton or not to singleton, that is the question....:
agged on to make it look a bit fancier
Not only! Using namespaces you're avoiding name collisions. And what does "global functions" mean here? If you include a C header with functions you also have "global functions". What you should avoid are global class instances/objects.
-
@JonB said in To singleton or not to singleton, that is the question....:
which you cannot do for class
You can put everything into namespaces, including classes. Or did I misunderstand you?
-
@jsulm said in To singleton or not to singleton, that is the question....:
Using namespaces you're avoiding name collisions.
namespace FooNamespace { void bar() {} }
versus
void FooNamespace_bar() {}
There really ain't much difference. That's why I say
namespace
is just a "prettier" way of writing what is still a global function, the two ways avoid collisions in a not totally dissimilar way! -
@jsulm said in To singleton or not to singleton, that is the question....:
You can put everything into namespaces, including classes. Or did I misunderstand you?
You do misunderstand. Please read again what I wrote in that question so I don't need to write out a full example again (though I will if you cannot get it). The question is about the fact that you can "wrap" the whole of your
cpp
file (note: asking about the.cpp
not the.h
) implementation inside a singlenamespace Foo {
..... all the body of the .cpp stuff without writing prefixFoo::
...}
instead of writing each of your functions asFoo::bar()
. You have to to do the latter if it'sclass Foo
. You can choose to do the same versus wrap in anamespace Foo { ... }
if it'snamespace Foo
. Which do you prefer in your namespace.cpp
file function definitions?OK, to type it in: in the
.cpp
file (I'm not talking about the.h
file) for aclass Foo
we write:void Foo::bar() { ... } void Foo::baz() { ... }
We have no choice for a
class
. This also works ifFoo
is anamespace
instead of aclass
.For a
namespace
, but not for aclass
, we can alternatively write.cpp
as:namespace Foo { void bar() { ... } void baz() { ... } }
to drop the
Foo::
prefix against each definition. Do you prefer this fornamespace
s, or the sameFoo::
prefix way as required forclass
es?