Welcome, Guest. Please login or register.
Did you miss your activation email?

Login with username, password and session length

 
Advanced search

877244 Posts in 32852 Topics- by 24294 Members - Latest Member: RopeDrink

May 18, 2013, 11:50:11 PM
TIGSource ForumsDeveloperTechnical (Moderators: Glaiel-Gamer, ThemsAllTook)Playing it silly with Singleton and static
Pages: [1] 2 3
Print
Author Topic: Playing it silly with Singleton and static  (Read 2673 times)
Linus
Level 0
***


View Profile Email
« on: June 17, 2010, 02:37:06 PM »

I did something hugely unorthodox with singletons and lots of C++ magic today that I never thought would work, but in the end, actually managed to figure out!

So, I thought I'd share my story!

First, the problem.
I had a setup where I was building a menu and wanted to insert menu entries into it:
Code:
-> A
-> B
-> C

Each entry in the menu was a separate class, they were all stored in a single STL vector, and I was constructing it like this:
Code:
std::vector<BaseClass*> entries;
...
constructor(){
  entries.push_back(new A());
  entries.push_back(new B());
  ...
}

This was a total pain, because I wanted all objects that were inheriting a single base class added into this menu!
Code:
 ...
  entries.push_back(new Y());
  ...
  entries.push_back(new XC());
  ...

Not to mention I was expecting that I would soon want to move the entries out into a setting where I could just add entries to a list after compiling in some kind of configuration file:
Code:
 entries = {A,B,C,D,E,Q,R7,ZZ}

So, I thought, what better way of managing this than a factory which could construct the objects for me?
Code:
 Factory::getSingleton()->create("A");

That way, I could just add them to a single list somewhere, and be done with it!

But, this would still be a pain. Somewhere, in my code, I would still need a repetetive central structure:
Code:
 Factory::getSingleton()->addEntry(new Constructor<A>(), "A");
  Factory::getSingleton()->addEntry(new Constructor<B>(), "B");
  ...
That anyone who made a new class would have to find and add entries to!
No way!

My line of thought was that I would rather add a few more lines into each class and have them added automatically to the factory with the adding isolated to the class, rather than a central structure that everyone would have to modify.

Code:
 class A : public Base {
    [someConstruct]
};
Rather than
Code:
void addFactoryObjects(){
   [giganormousList]
}

So, how on earth would I do that?

Well, first thing's first. I needed a factory, and I needed to be able to access it from anywhere.
Code:
class Factory{
   std::map<std::string, Constructor*> constructors;
   static Factory _instance;
public:
   static Factory* getSingleton(){ return &_instance; }
   void addConstructor(Constructor *c, std::string s){ constructors[s] = c; }
   Base* construct(){ return constructors[s]->construct(); }
};
So, I built a singleton.

Singleton, thought I. They're constructed... without having to write any external code!
Gasp!
So, I thought I could add something into each class, that would add to my factory when I constructed it:
Code:
class Constructor{
   virtual Base* construct() = 0;
};
template<class T>
class ConstructorTemplate{
   Base* construct(){return new T();}
};

template<class T>
class ConstructorCreator{
   ConstructorCreator(){
      Factory::getSingleton()->addConstructor(new ConstructorTemplate<A>(), "name");
   }
};
class A : public A{
   static ConstructorCreator<A> creator;
};
Easy as pie! But, it leaves some things to be desired!
First, I needed to find the right name, and as per template rules, I couldn't do it in the template! (template <const char * > is not allowed!)
Which left me with the option of adding a function or a constant, I chose a function:
Code:
class A : public Base{
   static ConstructorCreator<A> creator;
public:
   static std::string getFactoryName(){return "name";}
}
Which was, of course, a bit more code to write, but I could still isolate everything to the class, my goal in the first place!

So, I would have hoped I was finished here, but I ran into a problem!
My code, it crashed!

Seemingly, my simplistic implementation of the singleton pattern worked against me!
Or, more accurately, constructing anything beyond a simple structure isn't done at compile-time, but run-time! This meant that the factory I hoped would always be there wasn't constructed when I tried to access it! Horror!

Fixing it was easier than you'd think, by going back to the original Singleton pattern:
Code:
class Factory{
   static Factory *_inst;
public:
   static Factory* getSingleton(){
      if(_inst == 0) _inst = new Factory(); //Multi-threading, what do you mean?
      return _inst;
   }
};
Factory* Factory::_inst = 0;

Magic! It worked!
But! I introduced a new problem! My factory wasn't destroyed when I quit! Think of the damage my uncleared memory could cause on soon to be completely invalid memory!

Solving this in the easy way was no fun:
Code:
main(){
   delete Factory::_inst;
   return 0;
}

And could prove problematic if I used other constructs, such as exit(int)!

How could I destroy the object when there was nothing that could do it for me? Or was there?
Oh yes. Static members.
Static members are handled very nicely by C++, since they are always destroyed when a program exits, meaning I could use a static destructor to destroy my static pointer!

Code:
struct Destructor{
 ~Destructor(){ if(Factory::_inst) delete Factory::_inst; }
};

All I had to do, was at some point create it:

Code:
class Factory{
   ...
   static Destructor destroyer_of_worlds;
};

With this, I could efficiently destroy any singleton pointer before exit, which was perfect for my needs!
(Also, I found it funny how all my factory management is done before and after the main loop.)

With all of this finished, I could build my program:
Code:
int main(int, char**){
   Factory::construct("Pie")->eat();
   return 0;
}

This was my foray into static objects, I had fun, I hope you found it interesting.  Gentleman

Also, I'm curious as to how people react to a construct like this. I'm isolating all necessary data to move an object from being constructed via an explicit constructor to being constructed implicitly by a factory, meaning I'm containing what is necessary for a specific event to occur in a place related to the class causing it, so I'm adhering to some sort of object orientation principle.
At the same time, I'm using a couple of constructs I've never seen before and storing a few static instances during run-time for no real reason.
Is this bad code, horrible obfuscation, good object oriented isolation of behavior, totally insane or, as I see it, just slightly, slightly awesome? Cool


Linus
Logged
Pirate Hearts
Level 5
*****


PostCount++;


View Profile WWW Email
« Reply #1 on: June 17, 2010, 03:00:23 PM »

Nice.  I like the idea of using static destructor members to clean up singletons.  That's pretty clever.
Logged

BorisTheBrave
Level 10
*****


View Profile WWW
« Reply #2 on: June 17, 2010, 03:17:14 PM »

You should look into autoptr - it does exactly what your destructor does.

Otherwise, I don't see it as too evil - you are just working around a problem with open type systems.
I'd be more tempted to do it as a macro:
#define REGISTER_CLASS(ClassName)\
static bool ClassName##_registered = Factory::getSingleton()->registerConstructor(Constructor<ClassName>(), #ClassName);


But your way is fine, too.
Logged
Average Software
Level 10
*****

Fleeing all W'rkncacnter


View Profile WWW Email
« Reply #3 on: June 17, 2010, 05:30:55 PM »

I have this macro:

Code:
#define STATIC_INIT \
struct StaticInitializer\
{\
    StaticInitializer();\
} static_initializer;\
\
StaticInitializer::StaticInitializer()

Which is used like so:

Code:
namespace
{
    // Stuff that needs to be initialized in a complex way.

    STATIC_INIT
    {
        // Initialize the stuff.
    }
}

Adding destruction capabilities to it would be trivial, but I haven't needed those yet.

I still think singletons are better off buried as unit local data stores, and the outside interfaces should be namespaces with ordinary functions.  Much simpler interface to deal with.
Logged

Franchise - The restaurant wars begin!

What would John Carmack do?
Linus
Level 0
***


View Profile Email
« Reply #4 on: June 18, 2010, 10:15:48 AM »

Awesome. Static variables are one of those things I haven't delved much into during uni, only discovering it now during my thesis. Smiley

Checked out the std memory header as well, I had no idea it even existed, convenient!

I'm now intrigued as to why all the huge libraries using singleton objects commonly/always require entry and/or initialization via function calls or "new X();" constructs. I mean, they could just do it on their own, like the cool kids! Cool

Fun and interesting either way, just like template magic.
Logged
Klaim
Level 10
*****



View Profile WWW
« Reply #5 on: June 18, 2010, 11:26:40 AM »

I've used static destructor too somewhere in my big game, to be sure to automatically destroy one of the game's engine.

But don't forget one thing : static <-> not thread-safe.

That might be a problem with non-trivial architectures...

Logged

http://www.klaimsden.net | Game : NetRush | Digital Story-Telling Technologies : Art Of Sequence
Average Software
Level 10
*****

Fleeing all W'rkncacnter


View Profile WWW Email
« Reply #6 on: June 18, 2010, 11:36:26 AM »

I was really hoping the C++0x would add some kind of static initializer capability.  Ada and Java (I think C#) can do it, and Ada even goes the extra mile and lets you control static initialization order.
Logged

Franchise - The restaurant wars begin!

What would John Carmack do?
Skomakar'n
Level 10
*****


Vąutah


View Profile WWW Email
« Reply #7 on: June 18, 2010, 06:46:10 PM »

...

Couldn't you just have done something like this (pseudocode)?

Code:
class BaseClass
 {
    public:
        BaseClass()
            instances.push_back(this);

        static const std::vector<BaseClass *const> getInstances()
            return instances;

    private:
        static std::vector<BaseClass *const> instances;
 };
Logged

mak gam
Geisha Novia: Out now!
Bottoms Up!: Devlog

Royal Railway on Twitter.

Adam Emil
BorisTheBrave
Level 10
*****


View Profile WWW
« Reply #8 on: June 19, 2010, 01:21:27 AM »

He doesn't want one entry per instance, but one entry per subclass.
Logged
Skomakar'n
Level 10
*****


Vąutah


View Profile WWW Email
« Reply #9 on: June 19, 2010, 02:18:11 AM »

He doesn't want one entry per instance, but one entry per subclass.
Put a static boolean, check if it's false, add an instance, and set it to true, so that the selection will fail for every new object after the first one?
Logged

mak gam
Geisha Novia: Out now!
Bottoms Up!: Devlog

Royal Railway on Twitter.

Adam Emil
BorisTheBrave
Level 10
*****


View Profile WWW
« Reply #10 on: June 19, 2010, 02:42:05 AM »

That would be "one per subclass instantiated", not "one per subclass". You want this registry for for something like a factory, so what's the point if you have to ensure that at least one instance is constructed without using the factory. That means you still end up writing a line per class in main() or somehwere, and it's problematic as you are actually constructing the objects. Not to mention that you'd need a static boolean per subclass, so this suddenly requires as much work in the subclass as any of the previous methods.
Logged
Klaim
Level 10
*****



View Profile WWW
« Reply #11 on: June 19, 2010, 05:03:47 AM »

I was really hoping the C++0x would add some kind of static initializer capability.  Ada and Java (I think C#) can do it, and Ada even goes the extra mile and lets you control static initialization order.

You will have to wait for a module system to allow some kind of global initialization order. That'll be discussed again for the next-c++0x standard. You can read what was the last draft there : http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2007/n2316.pdf
Logged

http://www.klaimsden.net | Game : NetRush | Digital Story-Telling Technologies : Art Of Sequence
ChadyG
Manbaby
*



View Profile WWW Email
« Reply #12 on: June 22, 2010, 09:50:14 PM »

Just so you know, you made a Pluggable Factory

In other words, you came up with one of the more awesome design patterns on your own.  That's something to be proud of!
Logged
Jonathan Whiting
Level 2
**



View Profile WWW
« Reply #13 on: June 23, 2010, 01:11:01 AM »

Each entry in the menu was a separate class

It strikes me that this was your real problem.
Logged

bateleur
Level 10
*****



View Profile
« Reply #14 on: June 24, 2010, 12:59:43 AM »

SmileyHand Thumbs Up Right bateleur likes this.

But seriously - this is exactly the kind of thread I want to read more of. Thanks for posting it.
Logged

Pages: [1] 2 3
Print
Jump to:  

Theme orange-lt created by panic