JeroMiya
2007.05.16, 02:24 AM
(warning: parts of this story are over-dramatized :P )
I used to be a die hard C++ advocate, even after trying several other languages (Java, Objective C, C#, LISP, Ruby), but I still always fell back to C++ as my language of choice.
That is, until I started The Green Engine. This project, part of my senior design course at my university was (and is) the largest project I've created from scratch. And I quickly found that C++ was getting in the way.
The project contains a very simple custom scripting language and interpreter (designed for very inexperienced programmers). Internally, the engine uses various associative data structures which sometimes share references to the same objects. Knowing this would happen from the beginning I chose to use the boost library's shared_ptr template to handle reference counting unobtrusively for all shared objects in the engine. And that's where the trouble began.
You see, shared_ptr is a class template that is supposed to work much like a pointer, but it also keeps track of a reference count of the number of shared_ptr that are pointing to the same object, and automatically deletes an object when the last shared_ptr referencing it releases it (by going out of scope or being reassigned to another reference, or deleted itself).
Now, in the green engine, there are entities, behaviors, and messages, and all of them share some functionality. They all have a table of named variables (which are assigned integer keys for fast access), they all have "constructor" functionality in the scripting language (they can be instantiated with a parameter list, which runs a user-defined script on those parameters).
So, naturally, I wrapped all of this shared functionality into a virtual base class, from which the entity, behavior, and message objects derive. In addition, the "template" for a user-defined message/entity/behavior, and an "instance" of that message/entity/behavior are separated into separate classes. So far so good.
Now, there needed to be a place to store all the templates and instances in the user-defined game. So there are template tables and instance pools. Of course, there is a virtual base class for both template tables, and instance pools.
Here's where the problems start. The base classes for template tables and instance pools have to define methods that the subclasses override with some added functionality. Some of these methods have to return a reference (through a shared_ptr, naturally) to a template or instance. Now, since all templates and instances inherit from the same base class, the logical thing to do was return a reference to that base class type.
But shared_ptr is a template class, NOT a naked pointer. If you assign a shared_ptr<EntityTemplate> to a shared_ptr<Template> return value, it will either not compile at all, or worse, give a false reference count. Boost's solution was to require calling a template function to dynamically cast the shared_ptr<Template> into a shared_ptr<EntityTemplate>. Fine, except it looks like this:
boost::dynamic_pointer_cast<EntityTemplate, Template>(shared_ptr<Template> x)
which quickly makes code look messy.
Since the cast does look messy, I made it a point to write a small macro for it for each class, so instead of the above, I just have ENTITY_CAST(x) to do the dynamic_pointer_cast to turn x into shared_ptr<Entity>.
That's a little better, but now every time I want an entity template, I still have to do an ENTITY_CAST. So, instead I override all methods in the template and instance table subclasses that return a shared_ptr or take one as an argument, and make the return values shared_ptr<subclass>, and return the result of the base class's method, dynamically cast to a shared_ptr<subclass>.
Alright, so that works, but now I have to override pretty much every method of the base table classes just to dynamic cast the arguments or the return values. Even if I don't change anything.
So yeah, all of that effort spent just to get reference counting working in C++ that plays nice with templates and inheritance used at the same time. If I had used Java, I could have simply used the built in garbage collector and implemented my little shallow inheritance trees with no problem at all.
So, in short, even a die hard c++ user can, given the right motivation, change his mind about his favorite language.
The End,
JeroMiya
I used to be a die hard C++ advocate, even after trying several other languages (Java, Objective C, C#, LISP, Ruby), but I still always fell back to C++ as my language of choice.
That is, until I started The Green Engine. This project, part of my senior design course at my university was (and is) the largest project I've created from scratch. And I quickly found that C++ was getting in the way.
The project contains a very simple custom scripting language and interpreter (designed for very inexperienced programmers). Internally, the engine uses various associative data structures which sometimes share references to the same objects. Knowing this would happen from the beginning I chose to use the boost library's shared_ptr template to handle reference counting unobtrusively for all shared objects in the engine. And that's where the trouble began.
You see, shared_ptr is a class template that is supposed to work much like a pointer, but it also keeps track of a reference count of the number of shared_ptr that are pointing to the same object, and automatically deletes an object when the last shared_ptr referencing it releases it (by going out of scope or being reassigned to another reference, or deleted itself).
Now, in the green engine, there are entities, behaviors, and messages, and all of them share some functionality. They all have a table of named variables (which are assigned integer keys for fast access), they all have "constructor" functionality in the scripting language (they can be instantiated with a parameter list, which runs a user-defined script on those parameters).
So, naturally, I wrapped all of this shared functionality into a virtual base class, from which the entity, behavior, and message objects derive. In addition, the "template" for a user-defined message/entity/behavior, and an "instance" of that message/entity/behavior are separated into separate classes. So far so good.
Now, there needed to be a place to store all the templates and instances in the user-defined game. So there are template tables and instance pools. Of course, there is a virtual base class for both template tables, and instance pools.
Here's where the problems start. The base classes for template tables and instance pools have to define methods that the subclasses override with some added functionality. Some of these methods have to return a reference (through a shared_ptr, naturally) to a template or instance. Now, since all templates and instances inherit from the same base class, the logical thing to do was return a reference to that base class type.
But shared_ptr is a template class, NOT a naked pointer. If you assign a shared_ptr<EntityTemplate> to a shared_ptr<Template> return value, it will either not compile at all, or worse, give a false reference count. Boost's solution was to require calling a template function to dynamically cast the shared_ptr<Template> into a shared_ptr<EntityTemplate>. Fine, except it looks like this:
boost::dynamic_pointer_cast<EntityTemplate, Template>(shared_ptr<Template> x)
which quickly makes code look messy.
Since the cast does look messy, I made it a point to write a small macro for it for each class, so instead of the above, I just have ENTITY_CAST(x) to do the dynamic_pointer_cast to turn x into shared_ptr<Entity>.
That's a little better, but now every time I want an entity template, I still have to do an ENTITY_CAST. So, instead I override all methods in the template and instance table subclasses that return a shared_ptr or take one as an argument, and make the return values shared_ptr<subclass>, and return the result of the base class's method, dynamically cast to a shared_ptr<subclass>.
Alright, so that works, but now I have to override pretty much every method of the base table classes just to dynamic cast the arguments or the return values. Even if I don't change anything.
So yeah, all of that effort spent just to get reference counting working in C++ that plays nice with templates and inheritance used at the same time. If I had used Java, I could have simply used the built in garbage collector and implemented my little shallow inheritance trees with no problem at all.
So, in short, even a die hard c++ user can, given the right motivation, change his mind about his favorite language.
The End,
JeroMiya