Welcome, Guest. Please login or register.

Login with username, password and session length

 
Advanced search

1411977 Posts in 69438 Topics- by 58486 Members - Latest Member: Fuimus

June 15, 2024, 03:40:32 PM

Need hosting? Check out Digital Ocean
(more details in this thread)
TIGSource ForumsDeveloperTechnical (Moderator: ThemsAllTook)Playing it silly with Singleton and static
Pages: [1] 2
Print
Author Topic: Playing it silly with Singleton and static  (Read 4394 times)
Linus
Level 0
***


View Profile
« 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
J. Kyle Pittman
Level 6
*


PostCount++;


View Profile WWW
« 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
« 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



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


View Profile
« 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

Average Software
Level 10
*****

Fleeing all W'rkncacnter


View Profile WWW
« 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



What would John Carmack do?
oahda
Level 10
*****



View Profile
« 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

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
oahda
Level 10
*****



View Profile
« 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

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

ChadyG
TIGBaby
*



View Profile WWW
« 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

Average Software
Level 10
*****

Fleeing all W'rkncacnter


View Profile WWW
« Reply #15 on: June 24, 2010, 05:00:50 AM »

I not sure how well known this is, but I use a similar sort of technique when I need something akin to a finally block in C++.  Sometimes I need cleanup code that's more complex than what auto_ptr can provide, so I use a local object destructor, something like this:

Code:
void SomeFunction()
{
    struct Finalizer
    {
        Finalizer(Obj thing_to_clean)
        :thing_to_clean(thing_to_clean)
        {
        }

        ~Finalizer()
        {
            // Do the clean up with thing_to_clean.
        }

        Obj &thing_to_clean;
    } finally(the_thing);

    // The rest of the code.
}

This is also a great way of faking nested functions, something I think C++ sorely needs.
Logged



What would John Carmack do?
muku
Level 10
*****


View Profile
« Reply #16 on: June 24, 2010, 05:58:54 AM »

I not sure how well known this is, but I use a similar sort of technique when I need something akin to a finally block in C++.  Sometimes I need cleanup code that's more complex than what auto_ptr can provide, so I use a local object destructor, something like this:

I think Alexandrescu and Marginean have proposed "scope guards" as a library solution for this kind of thing, though theirs also handles the case where you want to execute some code only in the case of failure (if an exception is thrown). Might be overkill for your simple example maybe, but I do like the idea of hiding implementation details (using a destructor to execute the code).

By the way, the D language has scope guards (for success, failure and unconditional ones) built in which makes this kind of thing very pleasant to do. It really changes the way you do error handling.
Logged
Average Software
Level 10
*****

Fleeing all W'rkncacnter


View Profile WWW
« Reply #17 on: June 24, 2010, 07:10:02 AM »

By the way, the D language has scope guards (for success, failure and unconditional ones) built in which makes this kind of thing very pleasant to do. It really changes the way you do error handling.

I had to learn Python for work, and although the more I use it, the more I come to hate it, it's try-except-finally-else construct is a nice way of handling that.

I come across this sort of situation very rarely in C++, so I'm not sure it needs something to explicitly handle these cases.  I still want nested functions, though.
Logged



What would John Carmack do?
muku
Level 10
*****


View Profile
« Reply #18 on: June 24, 2010, 07:57:53 AM »

By the way, the D language has scope guards (for success, failure and unconditional ones) built in which makes this kind of thing very pleasant to do. It really changes the way you do error handling.

I had to learn Python for work, and although the more I use it, the more I come to hate it, it's try-except-finally-else construct is a nice way of handling that.

It's terribly verbose though. Especially if you don't want to catch any exceptions, the following code is much more concise:
Code:
void foo()
{
  auto x = acquire();
  scope(exit) dispose(x);

  // do stuff here that might throw...
}
It has the additional advantage of grouping the acquisition and the disposal together logically, instead of tearing them apart.

Quote
I come across this sort of situation very rarely in C++, so I'm not sure it needs something to explicitly handle these cases.  I still want nested functions, though.

Yeah. D has those too Tongue  Wouldn't want to live without them anymore.
Logged
Average Software
Level 10
*****

Fleeing all W'rkncacnter


View Profile WWW
« Reply #19 on: June 24, 2010, 08:11:11 AM »

It's terribly verbose though. Especially if you don't want to catch any exceptions, the following code is much more concise:
Code:
void foo()
{
  auto x = acquire();
  scope(exit) dispose(x);

  // do stuff here that might throw...
}
It has the additional advantage of grouping the acquisition and the disposal together logically, instead of tearing them apart.

How is that any different than just using a local object?

Code:
void funk()
{
    Something thing;

    // Destroyed at end of scope.
}
Logged



What would John Carmack do?
Pages: [1] 2
Print
Jump to:  

Theme orange-lt created by panic