Migrating to C++ Classes
The C++ification Process
Here's the approach to "C++ification" (that is, converting GObject/NRObject classes to use C++ features and idioms) that I've been developing:
- adopt C++ inheritance
- migrate "method" functions to C++ methods
- migrate GObject signals to SigC++ signals
- convert GObject classes to NRObject classes
- migrate NRObject virtual methods to C++ virtual methods
- migrate NRObject casting macro casts to implicit upcasts or dynamic_cast downcasts
- convert NRObject classes to bare C++ classes
Going to C++ cross-platform
The final goal is to have the software running in native code in any platform among the most used ones. So, here's a proposition: let's choose a C++ GUI library designed from step zero to be cross-platform, that is free and stable. The name is wxWidgets.
Details to follow later.
Currently, most objects in the codebase use a C-based "class" system, either GObject or NRObject. Subclassing is done by simple inclusion, though there is additional class metadata and an object-system-specific type factory mechanism.
Sadly, the factories' approach for object memory management and initialization is incompatible with using "natural" C++ constructors or destructors. Neither can virtual member functions be used; the vtable pointer will affect the member offsets.
As a consequence, when we make that leap, we'll need to do it a subsystem (at least an entire class hierarchy) at a time.
Here are, I think, some basic steps for making many of the changes incrementally, rather than having to do them all at once:
- Phase 1: migrate compatible features; this means:
- migrate towards SigC++ signals for notifications
- 1. add SigC++ signals to objects, and add hooks so the existing notification mechanisms (GObject signals or SPActiveObject notifications) also trigger them
- 2. migrate all clients to use the SigC++ signals
- 3. remove the old notification, and have code emit the SigC++ signals directly
- Start using "virtual pad" objects for virtual functions. These guys are temporary measure -- basically just an object with a pointer to the appropriate SPObject subclass, and a bunch of virtual methods. This lets us keep the vtable out of the real class until we are safely switched away from GObject objects.
- make "member functions" (e.g. sp_object_href(SPObject *, SPObject *)) into real member functions (e.g. SPObject::href(SPObject *))
- Phase 2: switch to "real C++"
- use our own factory facility (replacing sp_repr_type_lookup+g_object_new)
- move initialization code to constructors, and cleanup to destructors
- convert casting macros to appropriate use of C++ casting operators
- Move virtual methods from virtual pads up to the real classes, and get rid of the virtual pads
- Phase 3: cleanup; move classes to Inkscape namespaces, remove remaining C-style casts
Note that until Phase 2 is complete for a given class, we need to be very careful not to use virtual methods in it, either directly or indirectly (virtual destructors, virtual base classes, or base classes with virtual method)...
More Historical Notes
At WorldForge we had a client called UClient that was written early in the project and that had been its first deliverable product. It was nifty that it worked, and it even had sound, animation, weather effects, etc. implemented, but by all opinions the codebase was sheer terror. Nearly all the developers who looked at it decided it would be "more time efficient" to start new clients from scratch. We gave them the go-ahead and all the support we could muster, but after 1-2 years they were nowhere close to replacing UClient. Starting over from scratch doesn't work well; it's harder than you think and more likely to fail spectacularly.
Having gone through all that, we chose a new approach. We took the existing UClient codebase and one of the prototypical but aborted clients that had been developed, and started slowly refactoring UClient to start to look like the model. Of course, UClient didn't end up exactly like the model, since people figured out new ideas along the way, but this approach allowed us to achieve the end result while always having a distributable, working copy of the code, that always did at least what the previous release did (more or less).
I can easily see us slip into the first approach with Inkscape, but feel strongly that the second approach, while less glamorous, would be much more likely to succeed.
This was why I was asking about what you thought of recompiling Sodipodi into C++. That would be the logical first step; switch the compiler from gcc to g++ and then work on fixing all the compiler errors. Get it to compile and cut a release. Then pick some subsystem that's in dire need of objectification, take a look at your prototype, and figure out how to change the codebase to make it resemble your codebase, and do it in a way that doesn't require you to change very many files. Shoot for ambitious but incremental steps that refactor it into the direction you want it.
UPDATE 2003-11-02: Well we've embarked on the C++-ification with full vigor. Currently we've licked the compiler errors and are down to linker errors.
The changes needed were:
- Rename variables named after C++ keywords like new and class
- Add casts from (void*) to correct type
- Make strings be implemented as gchar* instead of the mix of char*, guchar* and unsigned char*
- Add explicit casts for ints to correct enum types
Linker errors appear to be due to:
- Multiple definitions of structs
- Undefined references due to way C++ does function name mangling
- Undefined references due to other reasons
UPDATE 2003-11-18: I've found this insightful interview with the creator of C++
For anyone interested in learning C++, or in honing their understanding of it, Bruce Eckel's books are quite helpful. He focuses on getting you into the right mindset to take full advantage of the language, which is a very good thing. Plus you can either buy the books or download them freely.