Large-Scale C++ Software Design

Following up on a comment from Udi Dahan about physical design Len Holgate points out that

If you're working in C++ and you haven't read Large-Scale C++ Software Design (APC) by John Lakos then you really should.

This is definitly true. John Lakos’ book - beeing as far as I know the only one on the topic - is a must-read for each C++ developer.

Len is using Lakos’ book as an argument against my post about C++ still beeing needed? and further states:

You have to actively manage your dependencies all the time, no matter what language you're working in. If you don't do it in C++ you can end up in a hell of slow compiles and horribly entwined code that all needs to be rebuilt if you ever change one class. If you do it right you rebuild just what needs to be built and you depend on just what you need, and no more.

Again: true. However, each project contains some very fundamental libraries, nearly all other packages depend on. A custom string class comes to mind ;-), but there are other. Let me tell you a little bit about the application I’m developing at work to get things clear.

We follow a model driven development approach. This is: One should be able to model what we call domains in any UML-capable CASE tool from which some basic code is generated so that the new domain can be used right away without further manual work. This allows easy prototyping of new business domains and it futher allows adjustments of the application’s class model at runtime.

To achieve this, we needed to bring reflection to C++. Reflection - known from most modern languages like C#, Java, Ruby etc. - allows retrieving class level information from instances. .NET’s GetType() function is an example of this. Our reflection model itself is an implementation of the UML 2.0 superstructure.

We established three meta modelling levels M0 (instance level), M1 (reflection) and M2 (runtime modelling). For example, to participate from reflection, all business objects must be derived from a class called M0Instance.

At this point, it should be quite clear, that nearly all of our code in some way or another depends on what we called the Kernel, containing the levels M0 and M1. We of course applied the guidelines of Large Scale C++ Software Design, but still changes to any of the Kernel’s core interfaces lead to a massive recompile. Just to give you some numbers: 30 Minutes on Windows, 1 hour on Linux and 2 hours on HP-UX.

I was arguing that development in C++ takes a significant higher amount of time compared to other high level languages, and that the long compile time is one factor among others. I think this still holds. Of course, changes in basic packages should occur rather seldom, however, our application is evolving, and so is the Kernel.

As a side note, the massive compile time established a culture of “better don’t touch”. I was realizing this, when I found myself adding complicated, ugly, and similiar code (referenced further as “the shame”) in several application layers when extending the application’s user access control features. By just introducing one new function to the reflection level, all this code was made obsolete. I was thinking about this function when I added the shame the first time, I also thought about it the second time. I finally did it in the middle of adding the shame the third time…

Some last note: I was implementing the Kernel in C#, mainly to learn the language. This, including persistence, two simple domains and a basic generic UI framework, took me four to five days. The initial C++ Kernel development - without any UI stuff - took several man months.

Published: December 03 2005