Jump to content


Registering class names


11 replies to this topic

#1 geon

    Senior Member

  • Members
  • PipPipPipPip
  • 893 posts

Posted 04 September 2005 - 12:11 AM

I'd like to base my own fileformat on serialization. So I need to tag each "chunk" of data with the class name to correctly deserialize them. So far so good.

But on loading, the abstract factory (I guess that's a suitable pattern here) should recognize this tag. That seems kind-of tricky, as I don't want to hard-code the class names (and plug-in based solutions are impossible that way).

Somehow the I need to register the class name and type in the factory. Is this possible at all with standard C++? Could it be done in a clean way?

I found some extremely ugly macro-based approach, wich I did'nt even understand. But I rather hard-code it all than using that...

#2 bladder

    DevMaster Staff

  • Moderators
  • 1057 posts

Posted 04 September 2005 - 08:53 AM

Have you taken a look at C++'s rtti capabilities? First of all what level of hardcoding are you willing to have with your system? How ugly will you allow this system to be? Could you write out some sample usage of how you'd like to be able to deserialize a class (that should also show what kind of serialization technique you're using)

#3 bramz

    Valued Member

  • Members
  • PipPipPip
  • 189 posts

Posted 04 September 2005 - 12:50 PM

I guess you mean that you don't want to have makeFoo and makeBar in your abstract factory? Try using a map that links a string to a maker function. Then use string to select correct make function. However, that does mean the result type of the makers must be the same for all of them. So, you'll need to have some base class to use for that. Something like this:

class Base;
class Foo: public Base;
class Bar: public Base;

Base* makeFoo() { return new Foo; }
Base* makeBar() { return new Bar; }

class BaseFactory
{
public:
   typedef Base* (Maker*) ();

   void subscribe(const std::string& tag, Maker maker)
   {
     makers_[tag] = maker;
   }

   Base* make(const std::string& tag) const
   {
     MakerMap::const_iterator i = makers_.find(tag);
     if (i == makers_.end())
     {
        throw std::logic_error("no maker found");
     }
     return i->second();
   }
private:
   typedef std::map<std::string, Maker> MakerMap;
   MakerMap makers_;
}

int main()
{
  MyFactor factory;
  factory.subscribe("foo", makeFoo);
  factory.subscribe("bar", makeBar);
  Base* p = factory.make("foo");
}

Now your plug-ins can subscribe their own makers if they want.

Add some templates to the mix to make it a bit more general:

template
<
   typename BasePointer,
   typename Tag,
   typename Maker = BasePointer (*)()
>   
class Factory
{
public:
   void subscribe(const Tag& tag, const Maker& maker)
   {
     makers_[tag] = maker;
   }

   BasePointer make(const Tag& tag) const
   {
     MakerMap::const_iterator i = makers_.find(tag);
     if (i == makers_.end())
     {
        throw std::logic_error("no maker found");
     }
     return i->second();
   }
private:
   typedef std::map< Tag, Maker > MakerMap;
   MakerMap makers_;
};

typedef Factory< Base*, std::string > BaseFactory;

using RTTI to get the class' name isn't guaranteed to give same result on different compilers, so be carefull with that. Better is to get a little help of the precompiler there.

I hope this helps

DISCLAIMER: I didn't test the code, so it may contain some syntax errors :)
hi, i'm a signature viruz, plz set me as your signature and help me spread :)
Bramz' warehouse | LiAR isn't a raytracer

#4 geon

    Senior Member

  • Members
  • PipPipPipPip
  • 893 posts

Posted 04 September 2005 - 02:33 PM

Yes, I am looking for something along the lines of what Bramz described. (Thanks for the code!)

I have no problem hard-coding the class namnes (or some unique tag string) if it can be done in the respective class. (Not in the common factory/file loader.)

The problem with Bramz's example is that the class still needs to be initialized. How can I do that if the class is unknown by the main app... ?



What I like to do:

In file loader:

void FileLoader::LoadChunk(vector<char> DataChunk, string TypeTag){
 BaseSerializable* NewObject = Tag2Class[TypeTag]->CreateNewObject();
 NewObject->Desierialize(DataChunk);

 StoreSomehow(NewObject);
}



Is it possible to run a function call in the class implementation like I when I would initialize a static member?

In every deserializable:


/* class definition for MyDeserializable

Blablabla

*/

factory::subscribe("MyDeserializable - some k3w1 |24nD0m text to make it unique.", MyDeserializeable::CreateNewObject);


Maby I can initialize a dummy static member:

int MyDeserializable::Dummy = factory::subscribe("MyDeserializable", MyDeserializeable::CreateNewObject);


#5 bramz

    Valued Member

  • Members
  • PipPipPip
  • 189 posts

Posted 04 September 2005 - 03:15 PM

You can use that trick to subscribe your class to the factory, if that factory is of course a global entity. To do that, you can use a macro like this:

#define EXECUTE_BEFORE_MAIN(statement, unique_name)\
  bool execute_ ## unique_name() { statement; return true; }\
  const bool unique_name = execute_ ## unique_name();

EXECUTE_BEFORE_MAIN(
  factory->subscribe("MyDeserializable", MyDeserializeable::CreateNewObject),
  subscribeMyDeserializeable)

The trick is that it uses a function to initialize a constant boolean value with as side-effect your statement. This constant boolean has static storage as to speak, and it is dynamically initialized using that function before main is executed.

However, there's two things:

1. You have to make sure that factory (which will be some global thing with static storage) is initialized before any of those subscribtions is done. The order of initialisation of static variables is only known within a single TU (translation unit, aka cpp file): that order is from top to bottom. However, if you have two statics in different TU, it is unknown in what order they will be initialized. So, if your factory is initialized in one TU, and you subscribe a class to it in another TU, it may be that subscribtion is tried before initialisation of factory ... spooky things :)

However, there is a loophole: objects with static storage with static initialization (initilization with zero or a constant expression, thus not the result of a function) are initialized before the ones with dynamic initialization. So if your global factory is hold by a pointer, and you initialize it to zero, you know that this will be so before the subscriptions are performed. Then, while doing the subscription, you check if that pointer is null, if so, create a new factory. Then continue subscription.

2. If that subscription is put (using that macro) in the TU of your class, and you simply link that TU to the rest of your program with nothing else of that program including your class, it might be that the subscripion is never performed. At least, that's what I experience with MSVC.

That's because an implementation may defer the initialization of an object with static storage and dynamic initialisation to a point after the first statement of your main, but before the first function or object of that TU is used. Thus, that macro's name EXECUTE_BEFORE_MAIN is not so very correct. So, if you just link that TU to your program, no function or object of that TU is ever used, so that implementation may defer executing the subscription to a very late point :/

How to solve that? I don't know :/

DISCLAIMER: all that matter on objects with static storage is rather confusing to me. So I may have made some mistakes there.

hi, i'm a signature viruz, plz set me as your signature and help me spread :)
Bramz' warehouse | LiAR isn't a raytracer

#6 bladder

    DevMaster Staff

  • Moderators
  • 1057 posts

Posted 04 September 2005 - 05:34 PM

Quote

How to solve that? I don't know :/

I think the trick is to put it in a unique class instead of a unique function:

#define EXECUTE_BEFORE_MAIN(statement, uniqie_name) \
class unique_name { \
 unique_name() {
  statement;\
 };\
static unique_name unique_name ## _var()

But then you have the problem of the factory not being initialized first, but that can be solved by using a function that returns the global factory object. This will ensure that whenever you call that function, the factory object is initialized first.

Factory& factory() {
 static Factory f;
 return f;
}

EXECUTE_BEFORE_MAIN( factory().subscribe(...), you_unique_name );

That will make sure that your factory object is initialized before it is used to register a function. And the fact that you are using static initialization of a class instance will make sure that your variable "unique_name ## _var" is created pre-main.

Anyway geon, this static stuff really is confusing, here's some info on the static initialization problem, and some solutions as well. Dave Eberly of Geometry Tools (formerly WildMagic) had a really nice solution. I forget it now, but I'll check that out as well and let you know of his solution.

- Static initialization problem
the 3 FAQs under that one describe three different solutions with different trade offs.

And here's an entire page from the C++ FAQ Lite on a few different techniques for serialization

link

Quote

DISCLAIMER: all that matter on objects with static storage is rather confusing to me. So I may have made some mistakes there.

Heheh, Amen!

#7 bramz

    Valued Member

  • Members
  • PipPipPip
  • 189 posts

Posted 05 September 2005 - 01:01 PM

bladder said:

I think the trick is to put it in a unique class instead of a unique function:

#define EXECUTE_BEFORE_MAIN(statement, uniqie_name) \
class unique_name { \
 unique_name() {
  statement;\
 };\
static unique_name unique_name ## _var()

I don't really see what's the difference with my proposal. This is still dynamic initialisation, isn't it. Then the implementation can still defer it to a point after the first statement of main but before the first use of a function/object of the TU containing the EXECUTE_BEFORE_MAIN.

Quote

But then you have the problem of the factory not being initialized first, but that can be solved by using a function that returns the global factory object. This will ensure that whenever you call that function, the factory object is initialized first.

Factory& factory() {
 static Factory f;
 return f;
}

View Post


This is indeed one way to do it. Though you must be carefull about the end of the program. There's also an unknown order in which the static objects are destroyed. So, you shouldn't be using factory() in the destructor of another static object, because f may be destroyed prior to that.

A way to overcome that problem is this variant of the factory() function:

Factory& factory() {
 static Factory* f = new Factory;
 return *f;
}


This one will never destory the factory. That may seem to be a memory leak, but it actually is not, because it happens at the end of the program. All memory is freed anyway. However, if something important happens in its destructor (like printing goodbye on screen), this of course will not happen.


Bramz
hi, i'm a signature viruz, plz set me as your signature and help me spread :)
Bramz' warehouse | LiAR isn't a raytracer

#8 bladder

    DevMaster Staff

  • Moderators
  • 1057 posts

Posted 05 September 2005 - 04:28 PM

Quote

I don't really see what's the difference with my proposal. This is still dynamic initialisation, isn't it. Then the implementation can still defer it to a point after the first statement of main but before the first use of a function/object of the TU containing the EXECUTE_BEFORE_MAIN.

Yeah it is, sorry about that, got confused. You actually made my knees a little wobbly when you said "an implementation may defer the initialization of an object with static storage and dynamic initialisation to a point after the first statement of your main" :D

But anyway, do you have any more info on this? I've been going through the crt src to see what msvc is really upto - unfortuanetly it confused me even more.

#9 bramz

    Valued Member

  • Members
  • PipPipPip
  • 189 posts

Posted 05 September 2005 - 09:37 PM

bladder said:

Yeah it is, sorry about that, got confused. You actually made my knees a little wobbly when you said "an implementation may defer the initialization of an object with static storage and dynamic initialisation to a point after the first statement of your main" :)

But anyway, do you have any more info on this? I've been going through the crt src to see what msvc is really upto - unfortuanetly it confused me even more.

View Post


sorry about that mate! :) i've found this in the C++ standard. section 3.6.2 § 3. To be honest, I never noticed that paragraph myself before examining that section closely to answer this question. But I know MSVC2003 does some funky stuff that is related to this, since I experienced it myself. I had such an EXECUTE_BEFORE_MAIN in an TU that wasn't really used by anything else, except after it had registered itself to some factory. But of course, it never did register ...

Bramz
hi, i'm a signature viruz, plz set me as your signature and help me spread :)
Bramz' warehouse | LiAR isn't a raytracer

#10 geon

    Senior Member

  • Members
  • PipPipPipPip
  • 893 posts

Posted 05 September 2005 - 10:35 PM

Great stuff! I'll dig into this next weekend. (Don't we all hate to have a real life? ;-)

#11 bladder

    DevMaster Staff

  • Moderators
  • 1057 posts

Posted 06 September 2005 - 01:51 AM

bramz said:

sorry about that mate! :D i've found this in the C++ standard. section 3.6.2 § 3.

View Post


OK, yeah it's there alright. There's also a footnote there. But I'm not sure how valid footnotes are.

Quote

An object defined in namespace scope having initialization with side-effects must be initialized even if it is not used.

Now I'm guessing a class with a non-trivial constructor is considered "initialization with side-effects". So actually, if my understanding is correct, the macro with the class should actually work.

#12 geon

    Senior Member

  • Members
  • PipPipPipPip
  • 893 posts

Posted 22 March 2006 - 08:37 PM

It's been a while...

Anyhow, I played around with this code again, and it isn't working... :unsure:


I wrote this little macro, based on above posts:
#define DESERIALIZER_SUBSCRIBE(ClassName) \
class ClassName ## _SubscriptionHelper { \
  public: \
    ClassName ## _SubscriptionHelper() { \
      cFactory::GetInstance()->Subscribe(#ClassName, ClassName::CreateNewObject); \
    }; \
}; \
ClassName ## _SubscriptionHelper ClassName ## _SubscriptionHelper ## _Instance()

...wich is used like this:
class MySerializableA : public cSerializable{
  public:
    static cSerializable* CreateNewObject(){return new MySerializableA();}

    void Serialize(std::ostream& Data)const{Data<<"I'm Bar A!";}
    void DeSerialize(const std::istream& Data){}
};
DESERIALIZER_SUBSCRIBE(MySerializableA);

...And nothing happens! In my main function I have this code:
//  cFactory::GetInstance()->Subscribe("MySerializableA", MySerializableA::CreateNewObject);

  cSerializable* bar;

  bar = cFactory::GetInstance()->Make("MySerializableA");
  if(bar)
    bar->Serialize(cout);
  else
    std::cout<<"no bar...";
  delete bar;

wich works as expected if I uncomment the first line. So nothing should be wrong with the rest of the code?

Any ideas? :sad:





1 user(s) are reading this topic

0 members, 1 guests, 0 anonymous users