Advanced Programming
COS3711
Chapter 7: Libraries and Design Patterns
Libraries are groups of code modules, organized in a reusable way. This chapter discusses how they are built,
reused, and designed.
The term platform refers to a particular combination of hardware architecture, especially central processing unit
(CPU), and software framework, especially operating system (OS).
The task of translating each item in the high-level code into machine language, so that it can be executed on a
particular computer platform, is handled by a compiler.
Widespread acknowledgment of the value of code reuse has steadily increased the demand for (and production of)
code libraries that store useful, reusable, already compiled code so that programmers can exploit its functionality
without having to handle any of its source code. A library module is reused when you #include its header file,
which specifies its Application Programming Interface (API), in the appropriate source code module. When you
reuse an item from a library, it is the job of a linker , during the build process, to establish appropriate connections
between the item references in your compiled code and the item definitions in the compiled library code. The
resulting executable must find and dynamically link to the compiled libraries (called runtime libraries ) at runtime.
A lib is a file that contains one or more compiled files (called object files ) indexed to make it easy for the linker to
locate symbols (e.g., names of classes, class members, functions, variables, etc.) and their definitions.
C++ libraries can be packaged in a few different ways:
• An open source package
o Usually distributed as a compressed archive containing all source code and header files plus build
scripts and documentation.
• A dev package
o Sometimes referred to as a -devel package by Linux package managers, is usually distributed as
an archive containing a lib plus its associated header files. This format enables you to distribute a
library without its source code. Others can still compile their applications with it.
• A runtime library
o consists of a lib file without its associated headers, so it can only be used to execute an
application that has already been built with the library.
Building and Reusing Libraries
When we set up projects that reuse these libraries, we always assume that the shell/ environment variable
CPPLIBS (or %CPPLIBS% in Windows) has been properly set to contain the libs root.
The command
qmake -project
produces a project file that contains information based only on the contents of the current working directory. In
particular, qmake cannot know about external libraries that you may need to build your project. If your project
depends on an external library, you must edit the project file and add assignments to the variables INCLUDEPATH
and LIBS . After that, rerunning qmake -project clobbers those changes, so don’t.
,For example, suppose you develop an application that uses our dataobjects library. The header fil es are in
$CPPLIBS/dataobjects , and the lib shared object files are in $CPPLIBS . Then you must add the following lines to
the project file:
INCLUDEPATH += $$(CPPLIBS)/dataobjects # the source header files
LIBS += -L$$(CPPLIBS) # add this to the lib search path
LIBS += -ldataobjects # link with libdataobjects.so
Organizing Libraries: Dependency Management
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.
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.
the dependency between a reuser ClassA and a reuser ClassB with
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 .
In each case, it is necessary to #include ClassB in the implementation file for ClassA .
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.
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.
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
o 6 a Perl script that analyzes C/C++ code and produces a dependency graph.
• Makedep
o 7 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.
Installing Libraries
After a library has been written and tested, it is installed at the end of the build process in the directory specified
by the qmake variable DESTDIR . For example, the project file for our dataobjects library contains the following
relevant lines:
TEMPLATE = lib # Build this as a library, not as an application
DESTDIR=$$(CPPLIBS) # Place the compiled shared object code here
For library templates, q make can generate a Makefile with the install target so that the command
make install
will, after a successful build, copy the library to some particular location. For example, on a *nix platform, you can
add the following lines to the project file for dataobjects :
target.path=/usr/lib
INSTALLS += target
Then, provided that you have write access there, the command
make install
will copy the libdataobjects.so files and their associated symlinks to the directory /usr/lib , making that library
usable by anyone logged into that computer.
If you need to relocate a library, the procedure varies from platform to platform. In Windows, you can copy its .dll
file into an appropriate directory listed in your PATH variable.
During development, it is usually sufficient to make and install libraries in your CPPLIBS directory, and adjust
LD_LIBRARY_PATH appropriately. On a *nix system, if you have followed our advice and made consistent use of
the CPPLIBS environment variable, you only need to add the line
export LD_LIBRARY_PATH=$CPPLIBS
to your .bashrc file, below the line in which you define CPPLIBS .
, The environment variable QTDIR is not required by Qt, but we sometimes use it in different parts of the book to
refer to the base directory of an unzipped Qt tarball. It provides a convenient way to refer to the location of the
examples, tutorials, binaries, and libraries of Qt.
Now, for any class that you want to export to the DLL, simply place the macro between the class keyword and the
classname in the class definition:
class DOBJS_EXPORT DataObject : public QObject {
[ . . . ]
}
There is a thread on the subject at qtcentre.org that might save you a lot of time when you build your first DLL.
Exercise: Installing Libraries
In this exercise, you can see how to build and install some libraries.
For help installing Qt and MySQL on Windows with MinGW, check the QtCentre Wiki1.
• Create a directory especially for your C++/Qt work; e.g., ~/oop/projects/ .
• Download src.tar.gz from the link
• Unpack this tarball in the new directory. That should result in a libs directory with a number of
subdirectories including libs/dataobjects and libs/ customer .
• Examine the subdirs project file, named libs.pro , in the libs directory.
• It is designed to build the libraries and the tests.
• Feel free to comment out the libraries and tests you do not plan to use, but do not change the order of
the libraries.
• Create a shell/environment variable named CPPLIBS that contains the absolute path of your new libs
directory. For convenience, you can place the definition for this environment variable inside a shell script,
as demonstrated in here
export CPPLIBS=$HOME/oop/projects/libs
export LD_LIBRARY_PATH=$CPPLIBS
libs.pro
TEMPLATE = subdirs
CPPLIBS=$$(CPPLIBS)
isEmpty(CPPLIBS) {
error("Define CPPLIBS environment variable to point to this location.")
}
SUBDIRS += dataobjects \
actioneditor \
customer \
# metadata \
# sqlmetadata
Build the libraries from the libs directory in two steps:
1. qmake -recursive // creates Makefiles in $CPPLIBS and in each subdir.
2. make // builds the libraries and tests (per Hc).2
Verify that the libraries are built and that the shared object files (e.g., libdataobjects.so ) are located in the CPPLIBS
directory.
Lines that begin with drwxr-xr-x are directories. Lines that begin with lrwxrwxrwx are symbolic links. Use Google
to find out why each shared object file has three symbolic links.
1
http://wiki.qtcentre.org/index.php?title=Building_the_QMYSQL_plugin_on_Windows_using_MinGW
2
You may need to install some specialized Qt packages before building some of our libraries. For example, the
libphonon-dev package is required for our phononmetadata library. The linker will inform you of these
dependencies.