We have seen various implementation aspects for single classes. We have also seen designs with several classes, for example, design patterns and template metaprograms. If we develop a large application or library, we have to consider a new level of organization: How to distribute classes and functions over files -- and also bigger units of organization.
We distinguish between the logical design and the physical design. The logical design describes how to write a class or a function and how to relate them to each other. Physical design describes how to organize the code in files.
In large systems, it is crucial to keep the complexity managable. One measure of complexity is the dependency between different units. Specifically cyclic dependencies increase the complexity. Complex systems are hard to understand and hard to test. Compilation times and link times can grow unmanagable large for complex systems.
After some definitions, which basically set up a sane way of organizing source code into components, we see techniques how to analyse physical dependencies between packages and how to break up cyclic dependencies between packages.
A name in C++ has external linkage if, in a multi-file program, that name can interact with other translation units at link time. Examples are non-static function names, member function names, and non-static global variables.
#ifndef NAME_H
#define NAME_H 1
// here goes the body of the header file
#endif // NAME_H //
Some compilers als accept a #pragma once
statement. Nowadays, compilers also detect automatically
include guards, such that a second attempt to include this header
file will not even result in opening and scanning this file.
Thus, redundant include guards (those around include statements in
the including file as proposed in [Lakos96]) are superfluous.
A component y DependsOn a component x if x is needed in order to compile or link y. More specifically: Component y exhibits a compile-time dependency on x if x.h is needed in order to compile y.C. Component y exhibits a link-time dependency on x if the object file y.o contains undefined symbols for which x.o may be called upon either directly or indirectly to help resolve them at link time. Compile-time dependency almost always implies link-time dependency (see also [Page 127 ff., Lakos96]).
The IsA relation and the HasA relation from the logical design form always compile-time dependencies.
Let us take a closer look on testing. We assume a test-driver program for each component. The benefit of components (and the intended modularization) is hierarchical testing. We test each compononent in isolation, before we test components that depend on this component. Of course, this does not work if we have cycles in the dependency graph. All components participating in the cycle have to be tested at once and together. However, this shows also a way out of cycles between components; reorganize the parts that participate in the cycle into one compoment (since we do not care what happens within one component). Some of the possible ways of reorganizing a design are covered in the next section.
Furthermore, the link time for building all test drivers increases. We compare two worst cases for n components: First, each component depends on all other components, and second, no component depends on any other component. If we assume unit cost for each linking with one component, we get cost for linking all test drivers in the first case, and in the second case. However, each non-trivial realistic system will have dependencies. A well designed system will aim for a flat acyclic hierarchy, approximately shaped like a balanced tree. Total linking cost would be .
The cost for linking all test-drivers can be captured in a useful metric.
The Cumulative Component Dependency, CCD, is the sum over all components in a subsystem of the number of components needed in order to test each incrementally.
Derived metrics are the avarage component dependency, ACD = CCD / n, and the normalized cumulative component dependency, NCCD, which is the CCD devided by the CCD of a perfectly balanced binary dependency tree with the same number of components. The CCD of a perfectly balanced binary dependency tree of n components is .
The book [Lakos96] describes tools to analyse the dependencies of components and to compute these metrics automatically, assuming the above rules for packages have been followed. The sources for the tools are available at ftp://ftp.aw.com/cp/lakos/.
An examples: We are given a bunch of geometric objects, among others a rectangle in a component of the same name:
// rectangle.h
#ifndef RECTANGLE_H
#define RECTANGLE_H 1
class Rectangle {
// ...
public:
Rectangle( int x1, int y1, int x2, int y2);
// ...
};
#endif // RECTANGLE_H //
We also work with a graphical user interface and have a component with a
class for a window:
// window.h
#ifndef WINDOW_H
#define WINDOW_H 1
class Window {
// ...
public:
Window( int xCenter, int yCenter, int width, int height);
// ...
};
#endif // WINDOW_H //
We realize, that both represent (among others) a two-dimensional box
and we would like to be able to construct a rectangle from a window
and vice versa. A first attempt might just include the respective
constructors. But as a consequence, we have to include the respective
header files and have a cyclic dependency.
// rectangle.h
#ifndef RECTANGLE_H
#define RECTANGLE_H 1
#include "window.h"
class Rectangle {
// ...
public:
Rectangle( int x1, int y1, int x2, int y2);
Rectangle( const Window& w);
// ...
};
#endif // RECTANGLE_H //
// window.h
#ifndef WINDOW_H
#define WINDOW_H 1
#include "rectangle.h"
class Window {
// ...
public:
Window( int xCenter, int yCenter, int width, int height);
Window( const Rectangle& r);
// ...
};
#endif // WINDOW_H //
The dependancy graph looks like this:

Actually, if we follow the include statements and the include guards, we will find out that the solution does not compile yet, since we include the other header file before declaring the own class. We need to use forward declarations to solve this problem.
In fact, since we use the class Window only by reference in the Rectangle constructor, we do not need the full definition of the class Window, which we get by including the header file, but a declaration would be sufficient. The same is true for the class Rectangle in the Window header file.
// rectangle.h
#ifndef RECTANGLE_H
#define RECTANGLE_H 1
class Window;
class Rectangle {
// ...
public:
Rectangle( int x1, int y1, int x2, int y2);
Rectangle( const Window& w);
// ...
};
#endif // RECTANGLE_H //
// window.h
#ifndef WINDOW_H
#define WINDOW_H 1
class Rectangle;
class Window {
// ...
public:
Window( int xCenter, int yCenter, int width, int height);
Window( const Rectangle& r);
// ...
};
#endif // WINDOW_H //
However, in order to implement the constructors in the source files
rectangle.C and window.C the respectively other
header file has to be included again. The resulting dependency graph
shows that we still have the cyclic dependency between the
components. We just have reduced the compile-time dependencies, not
the link-time dependencies.

Definition: A subsystem is levelizable if it compiles and the graph implied by the include directives of the individual components (including the .C files) is acyclic.
Thus, our example so far is not levelizable. We will see now some techniques to break cycles and to make a design levelizable.
// boxutil.h
#ifndef BOXUTIL_H
#define BOXUTIL_H 1
class Rectangle;
class Window;
struct Boxutil {
static Window toWindow( const Rectangle& r);
static Rectangle toWindow( const Window& w);
};
#endif // BOXUTIL_H //
Definition: A function f uses a type T in name only if compiling f and any of the components on which f may depend does not require having first seen the definition of T.
Examples for in name only are reference and pointer types. Both definitions extend naturally for components.
Components that use objects in name only can be thoroughly tested, independently of the named object. Examples are container classes, nodes, and handles that just pass their data as pointers around.
// cell.h #ifndef CELL_H #define CELL_H 1 #includeHere it might be worthwhile to reimplement the small fraction we need from string, namely storing a dynamically allocated array of characters.class Cell { std::string d_name; // ... public: Cell( const char* name); const char* name() const; // ... }; #endif // CELL_H //
// cell.h
#ifndef CELL_H
#define CELL_H 1
class Cell {
char* d_name;
// ...
public:
Cell( const char* name);
Cell( const Cell& name);
~Cell();
Cell& operator=( const Cell& cell);
const char* name() const;
// ...
};
#endif // CELL_H //
However, it is arguable for this example that a dependency on strings
is not a big issue, and that the old C-style interface is actually a
bit clunky.
NAME
qsort - sorts an array
SYNOPSIS
#include
void qsort(void *base, size_t nmemb, size_t size,
int (*compar)(const void *, const void *))
The function pointer compar is the callback function.
However, callback functions are difficult to understand, debug, and
maintain.
Besides the obvious ones, we address two techniques for partial insulation in the next section. Two techniques for full insulation are covered in the section thereafter.
Sometimes, going from partial insulation to full insulation is very easy. But sometimes, the last 5 percent are the hardest and the most costly. Full insulation is usually not appropriate at the bottom layers of a library. Full insulation is appropriate at the higher layers of a library that are exposed to the users.
Major runtime costs for insulation can happen because inline functions are no longer possible, virtual dispatch tables can add another indirection, and dynamic allocation of memory is slow. Memory use can increase with dynamic memory or virtual tables.
A private member function can be changed to a static non-local function of the component. If the private member function can be implemented using the public interface of the class, we just need to add the this pointer as an explicit function argument. If the member function needs exclusive access to private member variables, references to the private member variables can be added to the function signature. The price could be a penalty in runtime: the extended function signatures with larger parameter list cost time when calling the function if the function is not inline.
Another technique for full insulation is an opaque pointer. The insulated class contains only one opaque pointer to its private data and no other member variables.
// Insulated.h
#ifndef INSULATED_H
#ifndef INSULATED_H
class Insulated_private;
class Insulated {
Insulated_private* d_data; // opaque pointer
public:
// ... constructors and member functions
};
#endif // INSULATED_H //
The .C file implements the private data type and all member functions
and constructors. The .C file can be changed and recompiled without
forcing any other component to recompile.
// Insulated.C
#include "Insulated.h"
class Insulated_private {
// ...
};
// ....