Quote:
An example where you might need this can be found here (last paragraph):
http://www.informit.com/guides/content.asp?g=cplusplus&seqNum=119&rl=1
I think that's an excruciatingly poor example, to be honest. I will of course admit to the fact that sometimes type-switching is acceptable. That example, however, is just poorly conceived as far real-world design concerns go; it's just there as a framework for explaining a use. There are a number of ways to handle that situation more elegantly and without relying on RTTI -- although its somewhat beyond the scope of this thread, they primarily involve around fixing the fundamental design flaws with TextFile and OnRightClick themselves, both of which appear to be designed to do far too much and couple together far too much functionality.
Quote:
I don't know that much about RTTI more than people telling me not to use it because it's slow etc.
Isn't it a bit of overkill to enable RTTI whn only used for a small sub-system?
People are silly. There is obviously a performance cost associated with it, yes. But it's generally not significant. Profiling of the specific situation is generally required to make that judgment. If you need RTTI, your only options are to use the built-in RTTI or roll your own, which would likely have all the same performance costs anyway; the only issue would be that you could control the impact and scope of those costs better. It's probably not worth worrying about.
Quote:
Well I don't exactly need to, but for the system that I'm doing now it would be very helpful.
Basicly it's something like a very lighweight factory.
I.e when saving an object I have something like this:
template <class T> void save(T* object);
The first thing stored is the type name of the object, then the object data is stored.
For loading I have something like this:
template <class T> T* load();
The loader reads the typename, looks it up in a map to find a creator function (that is pre-registered).
I could easily solve this by "forcing" all my objects to save the correct typename, but it's easier to mess up and requires more code.
Okay. A factory system has no need for RTTI or type-switching at all; in fact, to make a factory use either of them makes it a pretty poor factory implementation, because type-switching creates maintainability issues -- to extend the system, a case for a new type must be handled -- and avoiding that is one of the key benefits of most factories.
There are a number of articles around on creating abstract factory systems. Google can reveal a lot. Both libindustry and Loki have pretty powerful abstract factories that might be worth looking into.
In the most-general form, a factory is about creating a product based on a key. That key does not have to be the actual type of the object desired. Here's a simple example of such a system:
The primary interface to the factory will be called Factory. Factory will produce subclasses of Product on-demand, based on a key; it will do this by using the key as an index into a map of Producer subclasses (Producer is a class designed to, as the name implies, produce a Product). Factory will need a way of being informed about associations between concrete Producer instances. All told, Factory might look like:
class Factory{ std::map<std::string,Producer> producers; public: std::auto_ptr<Product> Create(const std::string& key) { return (std::auto_ptr(producers[key].ProduceInstance()); } void RegisterProducer(std::string &key,Producer producer); { producers.insert(std::make_pair(key,producer)); }};
The interface for Producer should be implicit by the above definition:
class Producer{ public: virtual Product* ProduceInstance() = 0;};
Product can look like whatever you want it to look like; its interface is relatively unimportant. Note that templates can be used to further generalize all this, but that might obscure my main points.
Now, to actually use this, you have to do three things:
1) Create a concrete Product subclass that you want to have created.
2) Create a concrete Producer subclass that does nothing but create a new instance of the desired concrete Product.
3) Register the Producer with the Factory.
It might look like:
class FooProducer : public Producer{ public: Product* ProduceInstance() { return new Foo(); }};
main() might contain the registration and factory construction:
int main(){ Factory factory; factory.RegisterProducer("fooKey",FooProducer()); Product *foo = factory.Create("fooKey");}
Now, the gut reaction among many is to argue that that since you have to create a new Producer subclass for every Product subclass, you're doing the same
amount of work as with type-switching. That's true.
However, one of the big advantages here is that you can (with a big of extra work, such as using templates to remove specific dependencies on the base classes and key type) build yourself a generic abstract factory that is extended from
client code, rather than the requiring modification (and recompiling, and redistribution, et cetera) of the
library code where the factory resides. This reduces coupling and generally results in a cleaner design, since the client specifics (the concrete types) are located with the rest of the client code and not with the reusable framework code. You've also removed the need to write explicit logical tests and leverage both existing code and the language itself to do a lot of the tedious gruntwork of resolving the appropriate function calls.
Now, there are definitely improvements to be had: my example is, in the grand scheme of things, rather crude. I would encourage you to look into existing abstract factory implementations (again, libindustry and Loki are probably good places to start) and see how they're implemented; they employ a number of interesting and clever tricks, optimizations, and generalizations that result in much better production code than my ad-hoc example.
Anyways. I got a bit carried away by all that, some of which I'm sure you know, and didn't address your specific issue fully. The above principals can be applied to your situation, and it does sound like you've done so to at least some extent. For your load and save functions, presumably you have to call some method on the input object to actually get at the serializable data within that object (I hope you're not just writing the object's bits directly to a stream!). Since that enforces an interface on the type T anyway, you may as well continue to extend that interface to have it provide a method for getting the objects "serialization key" (which could in your case just be the name of the class). You'll have to manually provide the value of this key yourself, perhaps via a static method (you wouldn't necessarily want RTTI do that for you anyway, as the values of its type name strings are implementation dependent).
Your save function would then write the value of that key to the stream, followed by doing whatever actual serialization you require. Your load function would then read that key and give it to the factory (which would be more or less identical in spirit to my implementation, except it take a parameter in the Factory::Create() and Producer::ProduceInstance() methods that was the binary data read from the file that the actual object is to be deserialized from).
If you're doing generic serialization work, you might also want to examine Boost's serialization library. It's complex, but you might be able to get some ideas from it.
[Edited by - jpetrie on May 4, 2007 11:34:20 AM]