The horrible and disgusting "static class"
It's something I've always been opposed to - creating a class, knowing full well I'll only create one instance of that class, or none at all. I've always been an advocate of the idea "only create classes if you want to create several instances of the class". It's what always kept me away from the idea of using LEO, instead of using the plain C interface.
How many "engines" are going to be running inside my game? Just the one, so why make an engine class? Despite saying this, for years I did have some of my own 1-instance classes - usually some sort of linked list (I know these exist in the STL libraries but I'm a bit of a nutter and like to reinvent even basic things). I'd set them up as classes because I wished to hide some data, such as the pointer to the head of the list, so that it was always valid and could never be tampered with accidentally. The private access specifier was great for this, and anything else I never wanted to call/access accidentally.
Sadly, these private members always showed up in intellisense and it even offered to autocomplete them for me, only to then tell me that I was accessing a member function that was out of scope. Not hugely serious, as it was only a quick few seconds to replace the bad function call with the right one, but a little irritating nonetheless.
The lergest problem I was running into was that I noticed I lacked consistency. Half of these list classes I'd designed with the intention of being "static classes" and the other half expected an instance. The was no compelling reason to have chosen either, and was probably determined each time by how much stamp glue I'd been sniffing in the previous hours. I turned to Google in an attempt to standardise my classes and find out in no uncertain terms which was the better way, and then update my classes to reflect this.
I soon found though, that really, in the land of C++, neither was really the right option. Static classes were a sort of "hack" solution for Java (which is what we were taught at my uni) to achieve the same thing that C++ achieves with anonymous namespaces. Well, I call them anonymous namespaces because it sounds fancier than unnamed namespaces which is their correct name.
Indeed, I'd always known that namespaces, didn't actually have to have a name (even though the word namespace kind of implies it does), but I'd never seen any advantage of them. Since discovering how to use them "properly", I sort of couldn't keep it to myself, so hopefully any other C++ users out there who thought the same as I once did, could learn something here.
Here's one of my list classes (header only), as a list. It's one of the simpler lists, which much more resembles an STL linked list, than some of my others do
class RaysList { public: RaysList(); ~RaysList(); void AddProjectile(RayObject * RayToAdd); void DeleteProjectile(RayObject * RayToRemove); void DeleteExpiredProjectiles(void); void DeleteAllProjectiles(void); RayObject * GetNextProjectile(void); RayObject * GetCurrentProjectile(void); bool EOL(void); void ToStart(void); bool IsEmpty(void); private: struct ProjectileListEntry { RayObject * AssociatedProjectile; ProjectileListEntry * NextListEntry; }; ProjectileListEntry * ActiveProjectileHead; ProjectileListEntry * MemToReadFrom; };
This one as it happens, has no statics (decide for yourself how much glue was involved beforehand), and it looks simple enough.
- There's a constructor to set the two private pointers so they're never pointing to invalid memory (0 being hardcoded to mean end of list, thus is valid)
- There's a destructor to free any pointers that were allocated, should the list fall out of scope
- The member functions are all public so they can all be called validly
But it can be re-written without a class, whilst still keeping the access specifiers. "How?" You might ask, because it's true that public, protected and private (well, and friend too) are all class access specifiers - they can't be used for structs or namespaces. Well, here's how:
(Header first)
namespace RaysList { void AddProjectile(RayObject * RayToAdd); void DeleteProjectile(RayObject * RayToRemove); void DeleteExpiredProjectiles(void); void DeleteAllProjectiles(void); RayObject * GetNextProjectile(void); RayObject * GetCurrentProjectile(void); bool EOL(void); void ToStart(void); bool IsEmpty(void); }
No pointers at all here, but somehow my functions are going to return some. The magic takes place in the definitions (the .cpp file)
namespace RaysList { namespace { struct ProjectileListEntry { RayObject * AssociatedProjectile; ProjectileListEntry * NextListEntry; }; ProjectileListEntry * ActiveProjectileHead = 0; ProjectileListEntry * MemToReadFrom = 0; } void AddProjectile(RayObject * RayToAdd) { ProjectileListEntry * NewEntry = (ProjectileListEntry *) malloc (sizeof(ProjectileListEntry)); NewEntry->NextListEntry = ActiveProjectileHead; NewEntry->AssociatedProjectile = RayToAdd; ActiveProjectileHead = NewEntry; } }
The rest of the functions haven't been included, but for this demonstration they don't need to be, this one function shows you how it works.
- At the top of the file (not shown) is a line to include the header file
- Again, we open a namespace with the same name, but immediately inside this namespace, we create another one, without a name
- Anything inside the RaysList namespace has total unrestricted access to the secret namespace
- So, because of this, the pointer declaration to the secret struct in the first line of AddProjectile() is valid
- But if were to try and declare a pointer to this struct from outside the namespace it wouldn't be valid, and it wouldn't even show up in intellisense, so you wouldn't even be tempted.
Even the lack of a destructor doesn't cause a problem, since the pointer variables are declared outside of functions, thus they never fall out of scope... So you don't have any memory leaking.
The only reason we might have wanted a class for this concept... Eliminated... I'll never create a "static class" ever again
So there you have it. If you want the same effect as the private access modifier, but don't need to create an entire object, there's a way.
Sadly, because I learned Java at uni, I always thought this should have been possible, but never knew how, because of course, in Java, everything is an object, even if you're only going to create one instance of a class.
21 Comments
Recommended Comments