7.2.1.  Organizing Libraries: Dependency Management

[ fromfile: liborg.xml id: liborg ]

A dependency between two program elements exists if one is reusing the other; that is, if building, using or testing one (the reuser) requires the presence and correctness of the other one (the reused). In the case of classes, a dependency exists if the implementation of the reuser class must be changed whenever the interface of the reused class is changed.

Another way of describing this relationship is to say that ProgElement1 depends on ProgElement2 if ProgElement2 is needed in order to build ProgElement1.

This dependency is a compile time dependency if ProgElement1.h must be #included in ProgElement2.cpp in order to compile.

It is a link time dependency if the object file ProgElement2.o contains symbols that are defined in ProgElement1.o.

We depict the dependency between a reuser ClassA and a reused ClassB with a UML diagram as shown in Figure 7.1

Figure 7.1.  Dependency

Dependency

A dependency between ClassA and ClassB can arise in a variety of ways. In each of the following situations, a change in the interface of ClassB might necessitate changes in the implementation of ClassA.

In each case, it is necessary to #include ClassB in the implementation file for ClassA.

In the package diagram shown in Figure 7.2, we have displayed parts of our own libs collection of libraries. There are direct and indirect dependencies shown. At this level of granularity we are concerned with the dependencies between libraries (indicated by dashed arrows).

Figure 7.2.  Libraries and their Dependencies

Libraries and their Dependencies

If you wish to reuse one of the libraries shown in Figure 7.2, you need to ensure that all of its dependent libraries are also part of your project. For example, if you use the filetagger library, there is a chain of dependencies that requires you to also make available the dataobjects library (e.g., FileTagger is derived from DataObject), the utils library (e.g., the test code generally reuses the various assert macros in utils), and the id3lib library (e.g., FileTagger has a member of type auto_ptr<ID3_Tag*> and defines some functions with parameters of type ID3_FrameID*).

Code reuse, a valuable and important goal, always produces dependencies. When designing classes and libraries it is important to make sure that we produce as few unnecessary or unintentional dependencies as possible because they tend to slow down compile times and reduce the reusability of your classes and libraries. Each #include directive produces a dependency and should be carefully examined to make sure that it is really necessary. This is especially true in header files: Each time a header file is #included it brings all of its own #includes along with it so that the number of dependencies grows accordingly.

[Note]Note

A forward declaration of a class declares its name as a valid class name but leaves out its definition. This permits that name to be used as a type for pointers and references that are not dereferenced before the definition is encountered. Forward declarations make it possible for classes to have circular relationships without having circular dependencies between header files (which the compiler will not permit).

In a class definition, one good rule to follow is this: Do not use an #include if a forward declaration will suffice. For example, the header file “classa.h” might look something like this:

  #include "classb.h"
  #include "classd.h"
  // other #include directives as needed
  class ClassC;    // forward declaration
  class ClassA : public ClassB {
    public:
      ClassC* f1(ClassD);
    // other stuff that does not involve ClassC 
  };

We have (at least) two intentional reuse dependencies in this definition: ClassB and ClassD, so both #include directives are necessary. A forward declaration of ClassC is sufficient, however, since the class definition only involves a pointer to that class.

Dependency management is an important issue that is the subject of several articles and for which a variety of tools have been developed. Two open-source tools are



[24] We discuss signals and slots in Section 9.3.2.