Not logged in, Join Here! or Log In Below:  
 
News Articles Search    
 

 Home / General Programming / C++ Guru's please take a look at this. Account Manager
 
Archive Notice: This thread is old and no longer active. It is here for reference purposes. This thread was created on an older version of the flipcode forums, before the site closed in 2005. Please keep that in mind as you view this thread, as many of the topics and opinions may be outdated.
 
Randall Foster

March 04, 2005, 02:13 AM

I've got a general programming problem I wonder if anyone could answer with an elegant solution.

I have a class A that provides a common interface for derived classes. Lets say classes B,C,D and E have been derived from A.

I have an array of A pointers and a file full of B,C,D,E s I'd like to load up. Lets say I have a char before each classes data in the file to specify what sort of class it is in the file.

Normally I'd have to have a huge switch in my loader so that I could determine what type of class to allocate. Here's a bit of pseudo code.

  1.  
  2. A* A_PointerArray[100];
  3.  
  4. for(i=0; i<NumObjectsInFile; i++)
  5. {
  6.     char Type = ReadInClassType(InputFile);
  7.    
  8.     switch(Type)
  9.     {
  10.         case 'B':
  11.             A_PointerArray[i] = (A *)new B;
  12.             break;
  13.  
  14.         case 'C':
  15.             A_PointerArray[i] = (A *)new C;
  16.             break;
  17.  
  18.         case 'D':
  19.             A_PointerArray[i] = (A *)new D;
  20.             break;
  21.  
  22.         case 'E':
  23.             A_PointerArray[i] = (A *)new E;
  24.             break;
  25.     }
  26.  
  27.     A_PointerArray[i]->LoadClassFromFile(InputFile);
  28. }
  29.  


Is there a way I can automatically allocate the correct type of class into A_PointerArray without the huge switch statment? I want to automatically create new classes that derive from A that can handle loading and allocating themselves without me having to go into the switch statment and add a new case. It would be cool if something existed that could take an int or lets say the classname and allocate from it. Like this perhaps.

  1.  
  2. A_PointerArray[i] = (A *)new GetClass(Type);
  3.  


Does anyone know a solution or mechanism that would allow me to do something like this? Thanks!

 
pauljan

March 04, 2005, 02:32 AM

I don't know the first thing about C++, and you probably want some solution on the meta-level, but the first thing I would do is replace that case statement by a factory :P

 
kiddygames

March 04, 2005, 02:34 AM

Hello,

it seems that what you want is an instance factory.

What you can do is for each derived class (B,C,D...) have a static method "Instanciate" returning a new instance :

  1.  
  2.  
  3. class B : public A
  4. {
  5. public:
  6.  static A* Instanciate(){return new B;}
  7. };
  8.  
  9.  


Of course, you can use a macro to add the same piece of code to all your classes.

Then you need a map to "register" your classes:

  1.  
  2.  
  3. // define InstanciateMethod to be a pointer on an Instanciate method
  4. typedef     A* (*InstanciateMethod)();
  5.  
  6. // the map
  7. std::map<int,InstanciateMethod>  myMethodMap;
  8.  
  9. // register a new class to the map
  10. myMethodMap[TYPE_B]=B::Instanciate;
  11.  
  12. // TODO with all your other classes
  13. ...
  14.  
  15. // then instanciate a new class
  16.  
  17. int Type = ReadInClassType(InputFile);
  18.  
  19. InstanciateMethod method=myMethodMap[Type];
  20. //! if class type is found then create a new instance
  21. if(method)
  22. {
  23.    A_PointerArray[i]=(method)();
  24. }  
  25.  
  26. A_PointerArray[i]->LoadClassFromFile(InputFile);
  27.  
  28.  


Of course, I just wrote the code without testing anything here, just to show you a way to do it.

Stéphane

 
Reedbeta

March 04, 2005, 02:49 AM

Basically, what you want is to associate char values with functions (in this case, functions that return a new instance of some class). There are several different ways to do this, but you are never going to get away from the need to add an additional "case" whenever you create a new derived class.

For instance, you could have an std::map that would associate each character value ('B', 'C', 'D') with a pointer to the appropriate function (B::Create, C::Create, D::Create as static member functions for instance). But somewhere you still need to initialize the std::map with a list of all the derived classes, and this will have to be modified if you add a new class. To some extent you may be able to automate this with macros, but there is no perfect solution.

 
Randall Foster

March 04, 2005, 02:50 AM

pauljan,

Totally want something on the meta level.

kiddygames,

Yes you could do something like that but your still having to map a new function call to each Type value for each new class you derive. Which is kinda what I'm trying to avoid. It's not really the speed of the switch that bothers me it's the fact that you need ot write something each time you derive a new class.

Trying to get away with not using STL as well.
If I wanted to do something like that I would just create an array of function pointers and fill it wit the correct function for each index. Although map is the same idea in principle.

Anyone else?

 
Randall Foster

March 04, 2005, 02:58 AM

Reedbeta,

Hmm I'm hoping to find a way to automate it. Perhaps there is a way to write something where the preprocessor will keep track of how many derived classes there are and create the array of function pointers itself. Or something to that effect. I've seen some people do nifty preprocessor stuff like this in the past! :)

 
Reedbeta

March 04, 2005, 04:11 AM

LOL...I said basically the same thing but I didn't see your post before I posted. Sorry about that.

 
Reedbeta

March 04, 2005, 04:22 AM

For whatever it's worth,

Half-Life 1 (and maybe Quake 1 as well?) uses a macro system which automatically writes an instantiate-function, whose name is based on the entity's name string - eg "func_wall" becomes Instantiate_func_wall() - for each derived class (entity) and exports it from the game DLL. The engine (where all the file loading code is) can then look up these functions using GetProcAddress, which takes a string parameter.

You basically want to do the same thing but without needing a separate DLL. In other words, you want to be able to declare certain functions (the instantiate functions) as "exported" and have them automatically (at compile-time) put into a list of some kind along with their names as strings.

Off the top of my head I don't know any elegant way to do this...it's an interesting problem, I'll have to think about it some more. But perhaps this helps to guide your own search for a solution. Or maybe I just restated what you already knew =D

 
kiddygames

March 04, 2005, 06:59 AM

Well, in fact we were writting our posts at the same time I think...

Stéphane

 
kiddygames

March 04, 2005, 07:27 AM

Reedbeta,

I agree that it seems difficult to make it totaly automatically.

Randall,

you can automate it a bit by giving additionnal parameters to your class A constructor :

  1.  
  2.  
  3. class A
  4. {
  5. public:
  6.  
  7. A(InstanciateMethod method,int type)
  8. {
  9.  // your constructor here
  10.  
  11.  ...
  12.  
  13.  // the macro doing the declare
  14.  
  15.  DECLARE_NEW_CLASS(type,method);
  16.  
  17. };
  18.  
  19. };
  20.  
  21. // and then when creating a new class
  22.  
  23. class B : public A
  24. {
  25. public:
  26.   AUTO_CONSTRUCTOR(TYPE_B,B);
  27. };
  28.  
  29.  


where AUTO_CONSTRUCTOR(type,classname) is a macro implementing a default constructor calling the base constructor, and implementing the Instanciate method.

Stéphane.

 
pisiiki3

March 04, 2005, 11:11 AM

What you are looking for is what is called a "generic factory", but I think that you don't need that complexity to solve your problem. You want to serialize (or write) arrays containing pointers to a base class, but this class can be specialized. The true problem comes when reading, because you need special code to instanciate the diferent classes that you have written.

You can automatize the problem if you add a static list of valid instances to your interface. But how is that archieved? Take a look:

  1.  
  2.  
  3. template <class T_>
  4. class CBuilderBase
  5.   {
  6.   public:
  7.     virtual T_ *Instance()=0;
  8.   protected:
  9.     static std::list<CBuilderBase<INTERFACE>*> builders;
  10.   };
  11.  
  12. template <class INTERFACE_, class FINALCLASS_>
  13.   class CBuilder:public CBuilderBase<INTERFACE_>
  14.     {
  15.     public:
  16.       CBuilder()
  17.         {
  18.         builders.push_back(this);
  19.         };
  20.       INTERFACE *Instance()
  21.         {
  22.         return new FINALCLASS_();
  23.         };
  24.     }
  25.  
  26. class CInterface
  27.   {
  28.   };
  29.  
  30. class CSpecializedInterface:public CInterface
  31.   {
  32.   public:
  33.     static CBuilder<CInterface, CSpecializedInterface> b;
  34.   }
  35.  
  36.  


As you can see here the last 2 classes are what you need. The static member CBuilder instantiates at program startup for each interface specialitation and stores an abstract builder in a list on CBuilderBase. When the program starts you will have a list of valid specialized builders for your CInterface. Adding rtti check of the class type name on the CBuilder and storing it also in the list will allow you to instantiate a specialized type via his type name using the CBuilderBase class, and all withoud writing a line ;)

Keep in mind that linker will ignore code if not used, so be carefull with the statics or the trick won't work.

Isaac Lascasas

 
Reda

March 04, 2005, 03:47 PM

  1.  
  2. class A {
  3.   //....
  4.   static A* create(char type) {
  5.     switch(type) {
  6.       //...
  7.       case 'X':case 'x':
  8.         return (A*)new X;
  9.     }
  10.   }
  11. }
  12.  


  1.  
  2. for(i=0;i<NumObjectsInFile;i++)(A_PointerArray[i]=A::create(ReadInClassType(InputFile)))->LoadClassFromFile(InputFile);
  3.  


not sure if it'll work but it should do

 
etienne2005

March 04, 2005, 05:41 PM


I beleive you take the problem the wrong way :-)

When you create a base class A and derived it into B
You still have a A Object that happen to have function append to it...
NOT the other way around...

It's not a B object that you add A function to it...
It's the opposite

That is the whole point of using class :-)

When you create B you derived it from A so thing about B has a A object

If your lost, don't worry your not alone :-)

Here is an example :
I have a bunch of CObject and I will derive class from it
Like CCube, CRectangle, CRectangle
The tree shape derive from CObject who is the base class
From now on you have to tell yourself that you have 3 CObject !

You get to HIDE the tree shape in the box call CObject
It's like putting the shape in 3 blue box call CObject
From now on you only manipulate the 3 blue box...

You save the 3 blue box and retreive the 3 blue box
YOU DONT CARE WHAT ARE IN THE BOX...

How do you get to use the function in the shape then ?

It's ''easy'' you create a Virtual function in CObject call Draw
class CObject{
virtual Draw()
}
this Draw do NOTHING, but it could do some general stuff
You would have to call it in the Shape class in the Draw funtion

In the derived shape you OVERLOAD that Draw function...
(you simply defined a function call Draw)
class CCube : public CObject{
Draw(){Draw a cube in that function}
}
You do the same for the 2 other shape
class CRectangle : public CObject{
Draw(){Draw a cube in that function}
}
class CTriangle : public CObject{
Draw(){Draw a cube in that function}
}

So you create 3 shape derived from CObject
CCube MyCube= new CCube;
CRectangle MyRectangle=CRectangle;
CTriangle My Triangle=new CTriangle;

Now you have 3 CObject !!!

You can Manipulate them has if CObject was the ONLY interface...
CObject Table[10];
Table[0]=MyCube;
Table[1]=MyRectangle;
Table[2]=MyTriangle;

And to draw...
for i=0 to i=2 {
Table.Draw(); } This Draw will call the Draw of the cube shape when i=0 This Draw will call the Draw of the Rectangle shape when i=1 This Draw will call the Draw of the Triangle shape when i=2 Basicaly your 3 CObject KNOW what Object they are... You Never have to tell it to the object... It's complicated but it's totally crucial that you see this :-) You manipulate the 3 CObject save it, load it... They KNOW what they contain...Don't thing of them by their shape... That is the whole idea being C++ programing... The tree CObject (don't call them shape) look like this: ---------------------- CObject virtual Draw() CCube Draw() ---------------------- ---------------------- CObject virtual Draw() CRectangle Draw() ---------------------- ---------------------- CObject virtual Draw() CTriangle Draw() ---------------------- When you call draw on their CObject interface they search to find a Draw implemented in the CCube, if their is one they use it They will not use the Draw in the CObject interface unless you call it yourself CCube::Draw(){ CObject::Draw(); //This will call the ''general'' draw in CObject //You don't HAVE to call it... Draw the Cube here (your in the cube interface) } I was like you, using ''type'' to create my class It's not the super way the C++ intent it to work :-) You still think in C :-)

 
Randall Foster

March 04, 2005, 07:59 PM

etienne,

You obviously haven't correctly understood what my question was. Go back and study it and see if you can solve the problem without using a case statment in the allocation to check what kind of object is sitting in the file. I also want this whole process to be automated when you derive a new class.

So far there have been some good partial answers to the problem.

pisiiki3,

But the best one ... which I'm pretty sure gives me enough information to solve the problem correctly is posted by pisiiki3. Scroll up and check it out.


Thanks to all that posted!

 
Reedbeta

March 04, 2005, 08:18 PM

I thought of doing something like this too. The only issue is that each static member object also has to be declared in a source file. If you have these declarations in different source files, then their construction order is undefined. For this to work, the CBuilderBase::builders lists must be constructed before any of the CSpecializedInterface::b objects, and I don't think that there is any way to ensure this except if you declare them all in the same source file. Which gets us right back to the OP's problem - he doesn't want there to be one place where he has to add code if he adds a new subclass to his class tree.

 
etienne2005

March 04, 2005, 08:54 PM


Thanks Randall for your kind reply,

You're right, I didn't answer the question at all :-)

I'm more a MFC type programmer, And I thought you try to load
without having to read the type of the object stored

And that would require a Serialization of some sort
(that part I've left out of my explanation)

In MFC you get to have all kinds of help with that (CArchive, CFile)
has you can see in this link:
http://msdn.microsoft.com/library/default.asp?url=/library/en-us/vccore98/HTML/_core_serialization.3a_.serializing_an_object.asp

That way you can Store and Load your CObject without using type
And then add different kind of object without having to change the loader part

Since I'm no C++ guru, and template programing is not for me
I'm gonna pass on this one :-)

 
cppkid

March 06, 2005, 06:02 AM

  1.  
  2. // in a header
  3. #include <string.h>
  4. #include <stdio.h>
  5. class A
  6. {
  7. public: virtual void PrintName()=0; // just a pure virtual function for arguments sake
  8. };
  9.  
  10. struct ClassInfo
  11. {
  12.         ClassInfo*      next;
  13.         A*              (*create)();
  14.         const char*     name;
  15. };
  16.  
  17. extern ClassInfo* g_classList;
  18.  
  19. template <class T> class RegisterClass // Templated class registration class
  20. {
  21.         ClassInfo       m_info;
  22.         static A* Create() { return new T; } // just create a T
  23. public: RegisterClass(const char* name) {
  24.                 m_info.name     = name;
  25.                 m_info.create   = Create;
  26.                 m_info.next     = g_classList;
  27.                 g_classList     = &m_info; // add m_info at head of g_classList
  28.         }
  29. };
  30.  
  31. class Factory
  32. {
  33. public: static A* Create(const char* name) {
  34.                 for (ClassInfo* info = g_classList; info; info = info->next)
  35.                         if (strcmp(name, info->name)==0)
  36.                                 return info->create();
  37.                 printf("Factory::Create class %s not foundn", name);
  38.                 return NULL;
  39.         }
  40. };
  41.  
  42. #define REGISTER_CLASS(C) static RegisterClass<C> s_register##C(#C) // Handy macro
  43.  
  44. // in a source file somewhere
  45. ClassInfo* g_classList = NULL;
  46.  
  47. // in another source file (B, C, etc. don't require headers)
  48. class B: public A
  49. {
  50.         virtual void    PrintName() { printf("I'm a Bn"); }
  51. };
  52. REGISTER_CLASS(B);      // RegisterClass constructor called before main setting up g_classList
  53.  
  54. // in main.cpp
  55. int main() {
  56.         A* a = Factory::Create("B");
  57.         a->PrintName();
  58. }
  59.  


As I mention above B, C, etc don't require headers if you only interact with objects via A's interface, thus reducing dependancies (and compile time). btw I compiled and ran the code above before posting to check it actually works.

 
pistachioed

March 06, 2005, 12:34 PM

Is the construction order consistent from build to build of the code? I think it is, but I’m not sure what the standard has to say.

If that’s the case, then the method given by pisiiki3 would work just fine for objects that aren’t going to be serialized in an old build and deserialized in a new one. So, if your objects are going into save-game files that you don’t want to invalidate every time you rebuild your projection, as Reedbeta said, you’ll need a slightly more involved method (maybe mapping your builder functions to GUIDs, instead of just pushing them to the back of a list). If you just want to write to a temporary file that won’t be used from instance to instance of your program, pisiiki3’s code is just what the doctor ordered. Also, if you’re sending stuff over a network *and the server and the client are compiled as a single executable* pisiiki3’s method should be fine.

 
Reedbeta

March 06, 2005, 02:34 PM

It's still not guaranteed to work, because the std::list has to be instantiated before any of the CBuilders, but this is not guaranteed. It doesn't actually matter what order the CBuilders are instantiated in, so long as the std::list already exists when they are constructed (since their constructors add items to the list).

 
cppkid

March 06, 2005, 04:32 PM

The order is not guaranteed/consistent across compilers. If a CBuilder is constructed before a std::list then you access uninitialised memory in the member data of the std::list (very bad). My solution (see top level thread) doesn't require stl and doesn't do any memory allocation in the pre-main code.

 
Reedbeta

March 06, 2005, 09:39 PM

Again...I don't think it's guaranteed that g_classList will be initialized to NULL before any of the RegisterClass constructors are executed. Even if it seems to work "most of the time" it's not guaranteed to do so all the time, or on all compilers. Maybe I'm wrong?

 
pistachioed

March 06, 2005, 10:10 PM

Reedbeta:
Very true. That particular problem can be averted, however, by wrapping the builders list in a static method:

  1.  
  2. typedef std::list<CBuilderBase<INTERFACE>*> tBuilderList;
  3.  
  4. static tBuilderList& builders() {
  5.     static tBuilderList* pBuilders = new tBuilderList;
  6.     return *pBuilders;
  7. }
  8.  


I believe this is a fairly common way of avoiding the “static initialization order fiasco,” to use the terminology of the C++ FAQ Lite.


cppkid:
Your solution looks like it addresses the problem quite nicely, though I’m not so sure about your claim that it doesn’t do any pre-main allocation. Both g_classList and the objects created with the REGISTER_CLASS(C) macro seem to be allocated before main is called.

Reedbeta, again:
Just read your post. While it seems to me that there definitely should be some problems with writing to g_classList before it’s initialized, I tried as hard as I could to break the code cppkid posted, but never got any errors. I guess since g_classList is just a tiny little pointer, though, it’s very possible that the memory location allotted for it was never in use any of the times I ran the program, or maybe the compiler I was using somehow checks for that kind of thing in simple cases and fixes it, or cppkid knows something we don’t ...

 
Reedbeta

March 06, 2005, 11:32 PM

Ahh, didn't think of doing that. =) That's a nifty little trick.

 
Randall Foster

March 07, 2005, 01:45 AM

Thanks to all that have posted so far...

I've almost fixed my problems, but I've got sort of a side question.
I've got everything compiling fine but I get a link error because the static variable is never made. (unresolved external)

Is this legal? If so how/where do you define the template's m_StaticOfA so it will be created for each new template typedef?

  1.  
  2.  
  3. template <class T> class A
  4. {
  5.         public:
  6.  
  7.                 T  m_SomeData;
  8. }
  9.  
  10. template <class T> class B
  11. {
  12.         public:
  13.  
  14.                 T       m_MoreData;
  15.  
  16.                 static A<T> m_StaticOfA;
  17. }
  18.  
  19.  


Thanks!

 
Reedbeta

March 07, 2005, 04:21 AM

  1. template <class T>
  2. A<T> B<T>::m_StaticOfA;
should work.

 
cppkid

March 07, 2005, 03:30 PM

This is where the details and history of C++ get in the way. g_classList is initialised static data, since it is initialised to a compile time constant. The value NULL is stored in the compiled program and g_classList is NULL before any code runs (including global constructors). The way the list is constructed is designed to be independant of order of construction and does no memory allocation. By memory allocation I mean dynamic memory allocation i.e. calls to new/malloc or calls from something which calls new/malloc like an std::list does. This is defined behaviour for C++, it doesn't vary with compiler.

For a std::list you could do this

  1.  
  2. std::list<Whatever>* g_list = NULL;
  3.  
  4. static std::list<Whatever>& getList() {
  5.     if (g_list == NULL)
  6.         g_list = new std::list<Whatever>;
  7.     return *g_list;
  8. }
  9.  

 
cppkid

March 07, 2005, 03:54 PM

g_classList is initialised static data (see my other post). If it makes you happier then in a similar vein you could modify my code to

  1.  
  2. static inline ClassInfo*& getClassList() {
  3.     static ClassInfo *classList = NULL;
  4.         return classList;
  5. }
  6.  
  7. template <class T> class RegisterClass  // Templated class registration class
  8. {
  9.         ClassInfo       m_info;
  10.         static A* Create() { return new T; } // just create a T
  11. public: RegisterClass(const char* name) {
  12.                 ClassInfo*& classListRef = getClassList();
  13.                 m_info.name     = name;
  14.                 m_info.create   = Create;
  15.                 m_info.next     = classListRef;
  16.                 classListRef    = &m_info;              // add m_info at head of g_classList
  17.         }
  18. };
  19.  
  20. class Factory
  21. {
  22. public: static A* Create(const char* name) {
  23.                 for (ClassInfo* info = getClassList(); info; info = info->next)
  24.                         if (strcmp(name, info->name)==0)
  25.                                 return info->create();
  26.                 printf("Factory::Create class %s not foundn", name);
  27.                 return NULL;
  28.         }
  29. };
  30.  

 
cppkid

March 09, 2005, 02:24 PM

Okay I had to check up on myself here and some clarification is probably in order. I checked in "The C++ Programming Language" by Bjarne Stroustrup. He says that global scoped variables which are initialised to a compile time constant (a literal, an enumerant, a constant) do not require run time initialisation (i.e. their value is stored in the program and not computed or constructed) and therefore accessing them in a global construction/initialiser will always be safe.

This implies an order of execution like this:

statically initialised data (data whose value is know at compile time)
dynamically initialsed data (e.g. objects with constructors) in a random order*
main()

* He says that within a compilation unit (a source file) the order of initialisation is the order in which variables are defined. Another example of dynamically initialised data is

  1.  
  2. const double sqrt2 = sqrt(2.0);
  3.  

 
This thread contains 28 messages.
 
 
Hosting by Solid Eight Studios, maker of PhotoTangler Collage Maker.