The fragile base class problem is a shortcoming of certain object-oriented language compilers, in which internal changes to an underlying class library can cause descendant libraries or programs to cease working. It is an example of software brittleness.

Cause

The problem occurs due to a "shortcut" used with many common OO languages compilers, a design feature that was kept when OO languages were evolving from earlier non-OO structured programming languages such as C and Pascal.

In these languages there were no objects in the modern sense, but there was a similar construct known as a "record" (or "struct" in C) that held a variety of related information in a single piece of memory. The parts within a particular record were accessed by keeping track of the starting location of the record, and knowing the offset from that starting point to the part in question. For instance a "person" record might have a first name, last name and middle initial, to access the initial the programmer writes thisPerson.middleInitial which the compiler turns into something like a = location(thisPerson) + offset(middleInitial). Modern CPUs typically include instructions for this common sort of access.

When object oriented language compilers were first being developed, much of the existing compiler technology was used, and objects were built on top of the record concept. In these languages the objects were referred to by their starting point, and their public data, known as "fields", were accessed through the known offset. In effect the only change was to add another field to the record, one that lists the various methods (functions), such that the record knows about both its data and functions. When compiled, the offsets are used to access both the data and the code.

Symptoms

This leads to a problem in larger programs when they are constructed from libraries. If the author of the library changes the size or layout of the public fields within the object, the offsets are now invalid and the program will no longer work. This is the FBC problem.

Although changes in implementation may be expected to cause problems, the insidious thing about FBC is that nothing really changed, only the layout of the object that is hidden in a compiled library. One might expect that if you change doSomething to do something else that it might cause a problem, but in this case you can cause problems without changing doSomething, it can be caused as easily as moving lines of source code around for clarity. Worse, the programmer has little or no control over the resulting layout generated by the compiler, making this problem almost completely hidden from view.

In complex object oriented programs or libraries the highest-level classes may be inheriting from tens of classes. Each of those base classes could be inherited by hundreds of other classes as well. These base classes are fragile because a small change to one of them could cause problems for any class that inherits from it (either directly or from inheriting another class that does). This can cause the library to collapse like a house of cards as many classes are damaged by a single change to a base class. The problem may not be noticed as the modifications are being written because the inheritance tree is extrememly complex.

Solutions

Languages

The best solution to the fragile base class problem is to write a language that knows the problem exists, and doesn't let it happen in the first place. Most custom-written OO languages, as opposed to those evolved from earlier languages, construct all of their offset tables at load time. Changes to the layout of the library will be "noticed" at that point. Other OO languages, like Self, construct everything at runtime by copying and modifying the objects found in the libraries, and therefore don't really have a base class to be fragile.

Another solution is to write out an intermediate file listing the offsets and other information from the compile stage, known as meta-data. The linker then uses this information to correct itself when the library is loaded into an application. Platforms such as .NET do this.

However, the market has selected programming languages such as C++ that are indeed "position dependant" and therefore exhibit FBC. In these cases there are still a number of solutions to the problem. One puts the burden on the library author by having them insert a number of "placeholder" objects in case they need to add additional functionality in the future. This solution works well until you run out of these dummies -- and you don't want to add too many because it takes up memory.

Linkers

Another solution requires a smarter linker. In Objective-C, the library format allowed for multiple versions of a single library and included some functionality for selecting the proper library when called. However this was not always needed because the offsets were only needed for fields, since methods offsets were collected at runtime and could not cause FBC. Since methods tend to change more often than fields, ObjC had few FBC problems in the first place, and those it did could be corrected with the versioning system. The TOM programming language has extended this even further, using runtime collected offsets for everything, making FBC impossible.

Using static instead of dynamic libraries where possible is another solution, as the library then cannot be modified without also recompiling the application and updating the offsets it uses. However static libraries have serious problems of their own, such as a larger binary and the inability to use newer versions of the library "automatically" as they are introduced.

The vast majority of programming languages in use today do nothing to protect the programmer from FBC. This is somewhat surprising, as the problem has been known about since the 1980s. Yet even with a decade of problem discussion and solutions, the Java programming language nevertheless exhibits FBC with no support to avoid it.

Architecture

In these languages the problem is lessened by enforcing single inheritance (as this reduces the complexity of the inheritance tree), and by the use of interfaces instead of base classes with virtual methods, as interfaces themselves do not contain code, only a guarantee that each method signature the interface declares will be supported by every object that implements the interface.

External Links