Tips for extending C++ base classes while maintaining compatibility with derived classes

Would creating or adding methods to a derived method be just as easy?

Could you describe the mechanism you want to add as a new library? As a new derived class? If that is the case, why bother changing the base class? The part of the mechanism with generic potential can be migrated to the base class at a later date if desired. Trying it out in one specific derivation first can give time for the API to mature before wider use.

Add new methods to a base class without too much fear

Extending C++ base classes can be as simple as adding new methods to an existing class while preserving older methods. Avoid adding methods that ambiguify existing methods, i.e. same name but not distinct enough arguments. The rules for overloading virtual methods are more restrictive than for overloading non-virtual methods. Resist the urge to remove older methods if existing classes are already widely published and used. Add new methods in a way that respects the existing implementation, by leaving it unchanged, or by carefully and logically transforming it to be an equivalent capability.

Add new defaulted arguments to a method, and ensure that the default value causes the method to behave the same as before.

Like a boolean argument that defaults to false, or a pointer argument that defaults to nil. Only when the new argument is true (or non-nil) is new code exercised within the method.

Rely on "make depend", but when it fails, be ready to do a "make clean"

If the dependencies section of a C++ makefile is up to date, changes in a base class definition will cause derived classes to recompile. You only need to re-run "make depend" when new include files are added to an existing C++ module (at least with ivtools, where dependencies are kept in a separate Makefile.depend). Great amounts of recompilation can sometimes be manually avoided (by make'ing within a subdirectory) when the changes to base classes do not affect the memory layout of the C++ object, i.e. the addition of a non-virtual method, or other things like new defaulted arguments and new enums and constants. When all else fails, try a "make clean". There are holes in the "make depend" safety mechanism, like it can't detect that a fresh object file is incomplete because a previous compile ran out of disk space. This is usually indicated when stepping into a method with gdb ends up somewhere completely unexpected.

Rely on gcc and M-x next-error to find and correct all compile-time problems

gcc will find all compile-time errors created by any extension of a C++ base class. If you think of the C++ header files as a compilable design, then finding and resolving compile time errors (made easy with emac's M-x compile and M-x next error) is the equivalent of removing all the bugs from your design.

Rely on gdb to find and correct run-time problems

If a C++ program is compiled for debug (with -g and maybe without -O), and run with gdb through emacs, any encountered segfault causes the C++ module to be loaded into a buffer and the cursor set to the line of code where the problem occurred. Many problems can be quickly detected and fixed with gdb and emacs, like programming logic that doesn't test for a nil pointer or doesn't do the loop conditional checking as needed. Within emacs a M-x compile can rebuild the target file, and gdb will reload just that changed library before restarting. Other bugs like using or freeing an already freed pointer can be hunted down with some combination of the variable display or watch commands of gdb, and the regular commands for inspecting the state of variables and single-stepping after breakpoints.

One way to debug new/malloc heap problems is to link in a source version of malloc.c by adding an entry for it in the Imakefile of an executable, then doing a "make Makefiles;make". More information on heap debugging.

up to more Vectaport Technical Info

Copyright 1998 Vectaport Inc.