Engineering Logs - Singletons Revisited

Posted by Astryl on Feb. 26, 2015, 3:28 a.m.

Now that I have free time again for my own programming projects, I decided to start working on games again, and thus on my game engine.

My current goal is to transfer the gameplay from that puzzle/RPG game that I was working on in collaboration (Which is on halt due to the guy I'm working with being bogged down by the German education system).

So yeah, I'm quietly porting it to C++; we don't want to use GM for it, because it's damned messy.

Anyway, the last blog I wrote about Singletons showed me doing it in the naive way: Manually creating classes 'as' a Singleton type by giving it specific properties.

That works, but is a slog. Wouldn't it be far easier to just inherit some sort of global "Singleton" type and not have to do anything further?

Well, we can. And it's easy too.

There are two methods I'm going to address: One for those who like templates, and one for those who don't.

The Template method

Templates are a very powerful part of the C++ language… but in my personal opinion, they can also be messy, unwieldy abominations that make your code look like it fell part-way into a mincer.

It all depends on how you use them, of course.

Originally, I was just going to write the quick and dirty 'macro' technique, as below, but then on the spur of the moment I added this as an elegant alternative.

template<class ATYPE>
class Singleton {
    public:
        static ATYPE & getInstance() {
            static ATYPE singleton_instance;
            return singleton_instance;
        }
};

And that is pretty much it. Define your classes as such:

class Tester : public Singleton<Tester> {
    private:
        Tester(){} // Prevent construction
    public:
        void doSomething() { 
            printf("Something is being done\n");
        }
};

And use it like this:

int main(int argc, char** argv) {
    Tester::getInstance().doSomething();
}

Or, as I like to do it:

#define INST(singleton) (singleton::getInstance())

int main(int argc, char** argv) {
    INST(Tester).doSomething();
}

Works like a charm, easy to use, and easy enough to understand.

The Black Magic method (Macros)

First things first, Macros aren't by any means "bad". But there are "bad" ways to use them.

The C Pre-processor is a very powerful tool in the right hands; I use it to process my game scripts (Whether LUA or Squirrel), allowing me to use the ever-useful #include directive, as well as macros.

Macros, when used incorrectly, can cause all kinds of headaches for the developer; so use them at your own risk.

This, however, is an interesting little use for the things.

#define DECL_SINGLETON(stype) static stype & getInstance() { static stype singleton; return singleton; }
#define INST(singleton) ((singleton&) singleton::getInstance())

class Test2 {
    private:
        Test2(){}
    public:
        void doSomething() {
            printf("Something else is being done!\n");
        }

        DECL_SINGLETON(Singleton)
};

int main(int argc, char** argv) { 
    INST(Test2).doSomething();
}

Arguments for and against either method

Both of these methods work; they are almost equal in performance, identical in use… it's just a case of how you want to create your singletons.

Personally, I'm a fan of simply inheriting from the Singleton type. It's clean, elegant and easy.

On the other hand, you may be working in an embedded environment where templates may not be supported (Unlikely as that may be. Embedded systems are pretty sophisticated these days). Or, you know, you're using wxWidgets and already have a million macros sprinkled throughout your source.

So basically, flip a coin.

Footnote

I'll probably be posting a few updates on that game, specifically on the 'porting' process, within a week or two.

Though porting is a bit of a grand word to use; it's like creating a whole new game; I'm just reusing the art and game rules. :P

Comments

Nopykon 9 years, 9 months ago

Glad you've returned *from hell*. :) Neat, is all I can say. In this case I definitely prefer the template method.

Jani_Nykanen 9 years, 9 months ago

Reading someone's C++ code makes me want to code in C++, too. But when I try to program something in C++, it takes a few moments until I realize why I prefer JavaScript…

Astryl 9 years, 9 months ago

It's hardly right to compare JavaScript and C++. They're two completely different things. C++ is a programming language, JavaScript is a scripting language.

On that note, I'm working on a module to include JavaScript as a viable scripting language for this engine.

Toast 9 years, 9 months ago

C++ disgusts me, all the ** and the ::

Hideous.

But I should probably learn what those mean and give it a chance.

Cpsgames 9 years, 9 months ago

C++ is too spooky for me. I'll just stay with C#… for now.

Jani_Nykanen 9 years, 9 months ago

Quote:

It's hardly right to compare JavaScript and C++. They're two completely different things. C++ is a programming language, JavaScript is a scripting language.
Yeah, maybe I should have said 'C++ game programming with OpenGL' vs 'game programming with JavaScript and Html5'.

DesertFox 9 years, 9 months ago

Quote:
First things first, Macros aren't by any means "bad". But there are "bad" ways to use them.

The C Pre-processor is a very powerful tool in the right hands; […] when used incorrectly, can cause all kinds of headaches for the developer; so use them at your own risk.

QFT. Pretty much everything. Learning to use macros properly is a valuable skill. Stock modern C++ devs tend to look negatively on macros, because they were taught that it was 'bad' in college. This was usually justified in that A) using macros places a lot more responsibility, on the developers both defining and using them, to make sure things are correct (which is true but a non sequitur as an argument; requiring more care != bad) and B) C++ provides other mechanisms for metaprogramming (which is another non sequitur as this just means alternatives exist, not that either is better). Macros fill a specific niche, and they are powerful, dangerous, and require careful thought when developing. But the same can be said of rockets, and rockets are awesome.

C devs know the truth :P Pretty much all significant lines of code in my project are define, are, or contain macros.

Quote:
C++ disgusts me, all the ** and the ::

Hideous.

Can I get an amen!

Technology makes the 'doing' better (driving progress as a whole), and aesthetics/ergonomics make the 'using' better (driving adoption of specific implementations). C++ is really good for doing, but fits a 'critical' role so it faces less pressure on ease-of-use.

Astryl 9 years, 9 months ago

Quote:
C++ disgusts me, all the ** and the ::

Hideous.

** isn't too bad (Dereference of a dereference, shorthand for an array. Can be replayed, say, by: *varname[]).

I hate :: though, and often use Macros to hide their hideousness from general use. Seriously, the compiler should be smart enough to figure out this:

Singleton.getInstance(); // We're accessing the class object here
Singleton singleton;
singleton.getInstance(); // Accessing an instance
Singleton *singleton_p = &singleton;
singleton_p.getInstance(); // Accessing via pointer.

Made easier because of the strict typing in C++.

DesertFox 9 years, 8 months ago

Quote:
I hate :: though, and often use Macros to hide their hideousness from general use.
Don't forget, macros may be used to hide extra * and & as well.

I use macros extensively in personal projects (can't really at work - why? 'macros are bad' - but I use them sometimes anyway). I have a small suite of macros just for tracking development / incomplete tasks, and I use it everywhere:

#define LOG(FORMAT, ...) printf(FORMAT "\n", ##__VA_ARGS__)
#define WARN(FORMAT, ...) LOG("[!!!] " FORMAT, ##__VA_ARGS__)

#if DEBUG
#define DB(FORMAT,...) LOG("[DB] " FORMAT, ##__VA_ARGS__)
#else
#define DB(FORMAT,...) ((void)0)
#endif


#pragma mark - development macros

#define ONCE(body)  {   \
    static int __once = 1;  \
    if (__once) {   \
        {   \
            body;   \
        }   \
        __once = 0; \
    }   \
}

#define TODO(FORMAT, ...) ONCE({ LOG("[TODO] " FORMAT, ##__VA_ARGS__); })

#define UNIMPLEMENTED   \
{   \
    ONCE({  \
        WARN("Unimplemented function: %s",__func__);    \
    }); \
}

#define INCOMPLETE(reason)  \
{   \
    ONCE({  \
        WARN("Incomplete function: %s Task: %s",__func__,(reason));    \
    }); \
}

#define DISABLED(desc)  \
{   \
    ONCE({  \
        INCOMPLETE("Disabled code");   \
        LOG("\tDescription: %s",(desc));   \
    }); \
}


#pragma mark - panic/control macros

#define EXIT { \
    LOG("Exiting program..."); \
    exit(0);   \
}

#define ABORT { \
    LOG("Aborting program..."); \
    abort();   \
}

Ferret 9 years, 8 months ago

I like C++ the way it is :(