[ fromfile: liborg.xml id: liborg ]
A dependency between two program elements exists if one reuses 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 change whenever the interface of the reused class changes.
Another way of describing this relationship is to say that ProgElement1 depends on ProgElement2 if ProgElement2 is needed to build ProgElement1.
This dependency is a compile time dependency if ProgElement1.h
must be #included
in ProgElement2.cpp
to compile.
It is a link time dependency if the object file ProgElement2.o
contains symbols defined in ProgElement1.o
.
Figure 7.1 shows the dependency between a reuser ClassA
and a reused ClassB
with a UML diagram.
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
.
ClassA
has a data member that is a ClassB
object or pointer.
ClassA
is derived from ClassB
.
ClassA
has a function that takes a parameter of type ClassB
.
ClassA
has a function that uses a static member of ClassB
.
ClassA
sends a message (e.g., a signal) to ClassB
.[55]
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 display parts of our own libs
collection of libraries.
There are direct and indirect dependencies shown. This section focuses on the dependencies between libraries (indicated by dashed arrows).
If you want 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., MetaData
classes are derived from DataObject
), and the taglib
library (e.g., filetagger
uses taglib to load metadata).
If you want to use sqlmetadata
, then you need QtSql, the SQL module of Qt.
Code reuse, a valuable and important goal, always produces dependencies.
When designing classes and libraries, you need to make sure that you 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.
In a class definition header file, one good rule to follow is this: Do not use an #include
if a forward declaration suffices.
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 };
There are (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, because the class definition only uses 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
cinclude2dot, a Perl script that analyzes C/C++ code and produces a dependency graph.
Makedep, a C/C++ dependency generator for large software projects that parses all source files in a directory tree and constructs a large dependency file for inclusion in a Makefile.
Generated: 2012-03-02 | © 2012 Alan Ezust and Paul Ezust. |