Archived

This topic is now archived and is closed to further replies.

stodge

Storing class names in a string?

Recommended Posts

stodge    144
Is there any way in C++ to store the name of a class in C++, and then use that string to create a new instance of that class? For example, if I have in make believe pseudo-code:
class baseObject
{
};

class testObject: public baseObject
{
};

ClassName = "testObject";
obj = new ClassName(); 
Is this possible in anyway? Thanks http://www.stodge.net - the powerhouse in personal commentary [edited by - stodge on October 3, 2002 11:24:50 AM] [edited by - stodge on October 3, 2002 11:25:37 AM]

Share this post


Link to post
Share on other sites
Fruny    1658
Not directly, no. C++ does not support reflexion. You need to create a factory.


    
#include <map>
#include <string>

using namespace std;


class BaseObject
{
public:
virtual ~BaseObject() = 0 {}
};

class Factory
{
public:
typedef BaseObject* builder_t();
typedef std::map<string, builder_t*> builder_map_t;

BaseObject* Create( const std::string& name )
{
builder_map_t::iterator itor = builders.find( name );
if( itor == builders.end() ) return NULL;
return itor->second();
}

Factory& Register( const std::string& name, builder_t* fn )
{
builders[name] = fn; return *this;
}

Factory& Unregister( const std::string& name )
{
builders.erase( name ); return *this;
}
private:
builder_map_t builders;
};

class TestObject : public BaseObject
{
public:
~TestObject() {}
};

BaseObject* CreateTestObject() { return new TestObject; }

int main()
{
Factory fact;
fact.Register( "TestObject", CreateTestObject );


BaseObject* obj = fact.Create( "TestObject" );

delete obj;
}



Edit: fixed code so it would compile.

Documents [ GDNet | MSDN | STL | OpenGL | Formats | RTFM | Asking Smart Questions ]
C++ Stuff [ MinGW | Loki | SDL | Boost. | STLport | FLTK | ACCU Recommended Books ]


[edited by - Fruny on October 3, 2002 11:47:09 AM]

Share this post


Link to post
Share on other sites
Stoffel    250
Look up RTTI and type_info. They're standard C++ features. Beware that you usually have to manually enable RTTI--the default is disabled in MSVC (e.g.).

Edit: oops, didn't realize he also wanted to use it with a class factory. Well, maybe you could use type_info anyway; ok probably not. =)

[edited by - Stoffel on October 3, 2002 11:36:51 AM]

Share this post


Link to post
Share on other sites
Fruny    1658
Alternatively, instead of assigning strings of your own, you could use typeid to recover RTTI data, including the class name. (Infortunately, that''s about all it can do for you).

Documents [ GDNet | MSDN | STL | OpenGL | Formats | RTFM | Asking Smart Questions ]
C++ Stuff [ MinGW | Loki | SDL | Boost. | STLport | FLTK | ACCU Recommended Books ]

Share this post


Link to post
Share on other sites
Fruny    1658
quote:
Original post by stodge
Awesome Fruny, thanks. I'll try to understand what you've written.



It's simple, really :

create a map (associative array) that associates string identifiers to a function that creates an object of the desired type. New names/functions are added to the map with Register(). Then when you want to create an object, take the passed string, look up the corresponding function in the map, call it and return the generated object.

Edit: note - I've fixed a few errors in the code above.

Documents [ GDNet | MSDN | STL | OpenGL | Formats | RTFM | Asking Smart Questions ]
C++ Stuff [ MinGW | Loki | SDL | Boost. | STLport | FLTK | ACCU Recommended Books ]


[edited by - Fruny on October 3, 2002 11:47:53 AM]

Share this post


Link to post
Share on other sites
stodge    144
Weird. I started a new project to try out some more ideas (taking it a step further) and I get lots of warnings like this:

c:\program files\microsoft visual studio\vc98\include\xtree(110) : warning C4786: 'std::_Tree,std::allocator >,std:air,std::allocator > const ,
msNetObject * (__cdecl*)(void)>,std::map,std::allocator >,msNetObject * (__cdecl*)(void),std::less,std::allocator > >,std::allocator* (__cdecl*)(void)> >::_Kfn,std::less,std::allocator > >,std::allocator >::const_iterator::_Inc' : identifier was truncated to '255' characters in the debug informat
ion


I've never seen this before. Any ideas what I'm doing wrong with VC6?

Thanks

[edited by - stodge on October 3, 2002 1:40:23 PM]

Share this post


Link to post
Share on other sites
Fruny    1658
Ignore it. It does not affect the way the program works.

VC''s debugging information cannot store class names longer than 255 characters. Given that std::string and std::map are both typedefs for more complicated (templated) names, the fully expanded names end up being longer than that limit. Hence the "identifier was truncated to ''255'' characters in the debug information".

So, yeah, it''s a VC limitation. If it really bothers you (and it does me ) disable it.
e.g. #pragma warning( disable : 4786 ) There is another related warning to disable, but I can''t remember the code.

Documents [ GDNet | MSDN | STL | OpenGL | Formats | RTFM | Asking Smart Questions ]
C++ Stuff [ MinGW | Loki | SDL | Boost. | STLport | FLTK | ACCU Recommended Books ]

Share this post


Link to post
Share on other sites
Fruny    1658
Hey, since you''re experimenting, think about that one for a while :


  
class BaseObject
{
public:
virtual ~BaseObject() = 0 {};
virtual BaseObject* Clone() = 0;
};

class MyObject : public BaseObject
{
public:
BaseObject* Clone() { return new MyObject(*this); }
};

...

BaseObject* obj = fact.Create( some_name );
BaseObject* clone = obj->Clone(); // creates a second object, with the same type.



Documents [ GDNet | MSDN | STL | OpenGL | Formats | RTFM | Asking Smart Questions ]
C++ Stuff [ MinGW | Loki | SDL | Boost. | STLport | FLTK | ACCU Recommended Books ]

Share this post


Link to post
Share on other sites
stodge    144
Let me take this a step further. What I ultimately need is a way of creating an object, which is derived from BaseObject, on the server.

Then the server sends the name of the new object over to the client, which then creates an exact copy of the object using this name.

The killer part is that I can''t hardcode any registering of objects, because I want to be able to add new derived objects without adding new register functions. If that makes sense!

I can''t see how this can be done with these examples.

Thanks again

Share this post


Link to post
Share on other sites
daerid    354
This is perhaps one of the only times that preprocessor MACROS are actually useful in C++

Use a macro to declare and instantiate a static class whose constructor registers the passed classname with the global object factory.

Share this post


Link to post
Share on other sites
Fruny    1658
quote:
Original post by stodge
The killer part is that I can''t hardcode any registering of objects, because I want to be able to add new derived objects without adding new register functions. If that makes sense!



You will still have to have the code for the derived class somewhere and, therefore, can put the builder function along with it. Since you seem now to be aiming for a Pluggable Factory (e.g. with new object classes in a dynamic library), you need to register the new classes and functions when the library is loaded.

The API changes depending on the operating system but, usually, a dynamic library ( dll, dso ...) can specify a function to be called when the library is loaded ( with LoadLibrary(), dlopen() ...). It is at this point that you register the builder functions for the objects provided by the library with the server''s factory.

I hope that was clear. Note that I''m not addressing the issues of actually implementing the classes within a library (hint: you can only call virtual or static member functions through a dynamic library boundary)

Documents [ GDNet | MSDN | STL | OpenGL | Formats | RTFM | Asking Smart Questions ]
C++ Stuff [ MinGW | Loki | SDL | Boost. | STLport | FLTK | ACCU Recommended Books ]

Share this post


Link to post
Share on other sites
Fruny    1658
quote:
Original post by daerid
This is perhaps one of the only times that preprocessor MACROS are actually useful in C++

Use a macro to declare and instantiate a static class whose constructor registers the passed classname with the global object factory.


This will not work, as there are no way (short of using a singleton pattern) to guarantee that the factory object would be initialised before the registrations objects. Aside from that detail, this can be a valid use of preprocessor macros ... but templates would make it work better


  
#include <typeinfo>

template <class Type> struct Registration
{
public:
Registration( Factory& fact )
{
fact.Register( typeid(Type).name(), &Builder );
}

static BaseObject* Builder() { return new Type; }
};


This relies on typeid and RTTI to provide the name of the class. The actual string returned by name() is compiler-dependent.

Documents [ GDNet | MSDN | STL | OpenGL | Formats | RTFM | Asking Smart Questions ]
C++ Stuff [ MinGW | Loki | SDL | Boost. | STLport | FLTK | ACCU Recommended Books ]

Share this post


Link to post
Share on other sites
Fruny    1658
quote:
Original post by stodge
I''m in waaaay over my head now!


Awwwww. Come on, it''s easy

The template parameter lets you choose what Type you are trying to register :

Factory& GetFactory()
{
static Factory fact;
return fact;
}

Registration<FooObject> RegisterFooObject( GetFactory() );
Registration<BarObject> RegisterBarObject( GetFactory() );

Then, within the class, all instances of ''Type'' are replaced with the appropriate type. Which means that Registration<FooObject>::Builder() executes ''return new FooObject;'', and so on.

typeid(Type) is a built-in operator that returns a typeinfo object. This typeinfo object contains (minimal ) data about Type. Which includes the name of the type in typeinfo::name().

More questions ?

Documents [ GDNet | MSDN | STL | OpenGL | Formats | RTFM | Asking Smart Questions ]
C++ Stuff [ MinGW | Loki | SDL | Boost. | STLport | FLTK | ACCU Recommended Books ]

Share this post


Link to post
Share on other sites
Shannon Barber    1681
quote:
Original post by Fruny
This will not work, as there are no way (short of using a singleton pattern) to guarantee that the factory object would be initialised before the registrations objects. Aside from that detail, this can be a valid use of preprocessor macros ... but templates would make it work better


You might be able to guarantee it by using a base-class that has a static factory in it and then deriving all the pluggable''s from this one.
Are statics guaranteed to be initialize prior to the construction of a class instance? of a baseclass?

Share this post


Link to post
Share on other sites
stodge    144
Ok, just to take this a step further and really delve into your brains, what about the following addition.

How could I do something similar but with the method; that is how could I tell the client which method to call on the object? I''m thinking of passing an object ID to the client which will then grab the instance of this object. It will then need to call the appropriate method as dictated by the server.

Many thanks!


http://www.stodge.net - the powerhouse in personal commentary

Share this post


Link to post
Share on other sites
Fruny    1658
quote:
Original post by stodge
I''m thinking of passing an object ID to the client which will then grab the instance of this object. It will then need to call the appropriate method as dictated by the server.



You can pass around a pointer to the member function. IF the member function is virtual, then it is really only an ID for the method, which can be passed from one machine to another (so long as the object layout is the same, that the apps have been compiled with the same compiler & so on). Then, you can call it with object.*member_pointer(); or object_pointer->*member_pointer().

If this doesn''t work, you can keep a static member function pointer table for the object class in the class (and, possibly, register it along with the builder function, or provide a virtual BaseObject method that returns a pointer to the table). Then use the ID as an offset into that table to get the pointer, and call it as above (that''s what the previous method does, only it directly uses the object''s VTBL).

Documents [ GDNet | MSDN | STL | OpenGL | Formats | RTFM | Asking Smart Questions ]
C++ Stuff [ MinGW | Loki | SDL | Boost. | STLport | FLTK | ACCU Recommended Books ]

Share this post


Link to post
Share on other sites
stodge    144
quote:
You can pass around a pointer to the member function.


You''re kidding! I would never have imagined that that was possible. Is that also possible cross platform or not?

Thanks


http://www.stodge.net - the powerhouse in personal commentary

Share this post


Link to post
Share on other sites
Fruny    1658
quote:
Original post by stodge
You're kidding! I would never have imagined that that was possible. Is that also possible cross platform or not?



No, you have to ensure that the object layout is exactly the same. That implies using the same compiler on both sides and therefore precludes having different architectures on both sides. Cross-compilers might work, but I would not bet on it.

And it's only possible for virtual member functions, as such a pointer is in fact an offset into the virtual function table (on implementations that use a vtbl... that is most of them.). However, in general, you cannot pass a pointer from an address space (process) to another. That includes saving them, and so on (that's also why member functions of objects that are in a DLL must be virtual).

quote:
From Stroustrup's "The C++ Programming Language, 3rd ed", p420
Because a pointer to a virtual member is a kind of offset, it does not depend on a object's location in memory. A pointer to a virtual member can therefore safely be passed between different address spaces as long as the same object layout is used in both. Like pointers to ordinary functions, pointers to non-virtual member functions cannot be exchanged between address spaces.



Documents [ GDNet | MSDN | STL | OpenGL | Formats | RTFM | Asking Smart Questions ]
C++ Stuff [ MinGW | Loki | SDL | Boost. | STLport | FLTK | ACCU Recommended Books ]


[edited by - Fruny on October 4, 2002 4:08:20 PM]

Share this post


Link to post
Share on other sites
stodge    144
Ah I see. Thanks. I don''t plan on trying with the same compiler, it would be GCC 2.9 and VC6. Still that''s interesting.


http://www.stodge.net - the powerhouse in personal commentary

Share this post


Link to post
Share on other sites