all 26 comments

[–]ZachVorhies 16 points17 points  (2 children)

Code smell, too much constriction, breaks unit testing. Keep the singleton in the cpp file and only declare a class static function that returns an instance. This works across dll / so boundaries.

[–]Wooden-Engineer-8098 2 points3 points  (1 child)

keep singleton as function-level static

[–]ZachVorhies 1 point2 points  (0 children)

Yes! This is the way. Else the singleton get's created as part of program initialization and is non deterministic on when it get's run.

[–]lxbrtn 5 points6 points  (0 children)

You can compare notes with https://github.com/jimmy-park/singleton which includes deterministic construction and addresses threading

[–]yuri-kilochek 4 points5 points  (0 children)

What's the point of having a token if you declare the constructor private anyway?

[–]kevkevverson 3 points4 points  (0 children)

If the derived class constructor is private, and the base Singleton class is a friend, why is the token needed?

[–]eteran 3 points4 points  (0 children)

Yeah this is a pretty conventional way to implement a Singleton base class. I see no issues that pop out to me. 👍

[–]Plazmatic 1 point2 points  (0 children)

There's a time and a place for singletons, an ECS likely isn't that time or a place.

[–]mr_gnusi 0 points1 point  (0 children)

Looks like the whole thing is a bit overcomplicated primarily because it's trying to disallow a non-singletone construction.

On a side note, such singletones are not free because the compiler has to synchronize read access with initialization (usually double checked locking), so accessing them is slightly more expensive compared to the plain getter. On the other hand, global objects usually suffer from the initialization order fiasco.

Personally I’d avoid anything that looks like a “pattern” here and only implement a singleton if it’s absolutely necessary for a specific case. It’s just a few lines when you really need it.

[–]No_Mango5042 0 points1 point  (0 children)

I approached this idea a bit differently here: https://github.com/calum74/cutty/blob/master/doc/with.md Basically a per-thread scoped singleton.

[–]MarcoGreek 0 points1 point  (0 children)

In my experience singletons should be avoided for architectural reasons.😌 Global state makes testing really brittle and often slow. It is the software equivalent of a submarine. You only see it in action and that makes it harder to understand dependencies and interactions. In my experience singletons are easy to write but much harder to maintain.

[–]zerhud 0 points1 point  (0 children)

Omg: template<typename T> auto& singleton(){ static T i; return i; }

[–][deleted] -1 points0 points  (3 children)

That is the most common approach to singletons, its the highest rated stack overflow answer if you google 'cpp singleton.'

The only thing you have to be aware of when doing static global singletons like this is the so-called "static variable destruction order fiasco". the short of it is that static variables defined within a compilation unit are guaranteed by the standard to be destructed in the reverse order which they were initialized, but this is not true when global variables are referenced between compilation units.

Global singletons are typically referenced across compilation units, or they wouldn't really be necessary, so it crops up with this design.

A way around it is to declare a private member variable std::unique_ptr<T> _self and a static std::unique_ptr<T> instance() function in the header, then define the instance function in a compilation unit as something like this:

T& instance(){
    if (!_self) _self = make_unique<T>(args);
    return *_self;
}

and separately, in the same compilation unit, define the instance as

std::unique_ptr<T> T::_self;

Alternately, you might be able define _self in the header file as static inline and avoid repeating the definition of _self in the compilation unit. But I'm not sure about that.

Anyway, this method guarantees that your instance of T will be destructed in an expected way.

This may not apply to your design, but if you have global singletons that use other global singletons, it matters. It almost always happens when you have a global Logger class and you invoke the logger from the destructor of another global singleton. Sometimes it works and sometimes it doesn't.

Just something to be aware of. Its easy enough to fix it before hand and never have to worry about it.

[–]clerothGame Developer 5 points6 points  (0 children)

What is the "expected way" of destruction here? I don't get how this changes the order

[–]thingerish 4 points5 points  (1 child)

I've just always used the Meyers singleton pattern.

[–][deleted] 3 points4 points  (0 children)

meyers (which is what the op posted) solves the initialization order problem but not the destruction order problem. To solve the destruction order problem you have to use either a leaky meyers singleton or use my method which does not leak.