NOTE:
These are a little sketchy...I'll fill in details 'soon'.
Why do we make libraries? It is to make our functions more re-usable. Even more specifically, it is to make re-using our functions easier.
So what is separate compilation? It is the process of having the library and application codes compile separately and then combine into a single executable. The compiler, you see, is actually a sequence of three stages: pre-processing, compilation/translation, and linking. The pre-processor does all those things like #include — anything with a # symbol at the beginning of it, basically. The compiler proper does the translation of C++ codes into a binary form the computer can understand and execute. And the linker puts together multiple binary files into the executable file (our .out file).
Each C++ file (.cpp) is compiled into a binary form (.o) and then these are put together by the linker into the executable (.out). We've not seen any of these individual binary files because we've only been writing a main application program. But the linker has been providing a valuable service by linking in system codes and those standard libraries. Now, however, it is time to start making our own libraries.
So how do we do it? First off, note that a single library actually consists of two files: an interface and an implementation. We've only seen the interface files thus-far because all the standard libraries provide their implementations in binary form and are linked in by the linker. But now that you'll be writing your own libraries, you'll need to create both files: interface and implementation. (And don't forget to cat them both during your script!)
The interface file (also known as a header file) tells other programs or libraries what facilities are available from this library. It is like a menu that others can choose from to know what constants, type definitions, and functions this library is making available to them.
Typically only constants, typedefinitions, and function prototypes would be found in an interface file. However, several features of C++ require us to place some code in the header file as well. In particular, inline functions cannot be prototyped and so must be defined in the interface file.
Due to the fact that complicated libraries will often include other libraries and it is possible for such a structure as:
+->lib A ---+ | | lib C \ / . ` / \ lib B | | | | +-----------+
Here library A needs something from library B and so includes its interface file. Library B needs a function or constant from library C and so includes its header. Sadly, library C needs a feature of library A and so includes that interface file. Now the compiler is stuck in a circle or cycle — includeing these header files in sequence over and over again.
To avoid this cycle, we place some other preprocessor directives in the interface file to force the compiler to remember that it has seen that header file before and make it realize that it need not include it again. These directives are fashioned like:
#ifndef _____SYMBOL_UNIQUE_TO_THIS_LIBRARY______ #define _____SYMBOL_UNIQUE_TO_THIS_LIBRARY______ // constants, typedef's, and function prototypes #endif
For the compiler to remember that it has seen the library before and to therefore not include it a second time, we have it ask: "Have I seen this symbol which is unique to the library yet or not?" If it has, it will skip to the #endif and be done. If it has not (the symbol is Not DEFined), it will define that symbol (remember it) and proceed with the remainder of the library's interface.
An easy and typical pattern to follow for choosing this unique symbol is to use the library's name with some extra verbage:
Library Contains | Library Name | Library Symbol |
---|---|---|
math functions | my_math | MY_MATH_H_INC |
generally useful functions | genfunc | GENFUNC_H_INC |
utility functions | util | UTIL_H_INC |
string functions | strextra | STREXTRA_H_INC |
So, you ask yourself, where's all the C++ code for these library functions? Well, that is in the implementation file for the library. This is a separate file where we hide all the code (and possibly other features we don't want others to see).
The interface file's extension is .h (taken from its alternative name "header"). The implementation file (being actual C++ code) ends in a .cpp extension.
So a library actually consists of two files: an interface and an implementation. One is the menu that tells what is available. The other is the kitchen where the meals are prepared.
The trickiest thing to remember about the implementation file is to include its own interface file before you start defining all those publicly declared functions (the ones prototyped in the interface file). The reason is that: you need to be sure you have the definitions for any types and constants that were shown in the interface file. You may also have functions in this library which call each other. Without having all their prototypes included first, you'd have to make sure you defined them in the proper order in the implementation file.
When you want to use your newly created library in a program, how do you do it? Well, you'll have to first include the interface file. This is a little different for your own library than for a built-in library. Instead of the angle brackets (<>), you use double quotes ("") to surround your header file name:
#include <iostream> #include "strextra.h"
Once that is done, the program will compile just fine. However, the second step of compilation (creating the executable) won't fare so well. Since the compiler doesn't have definitions for functions prototyped in the interface file, it cannot create a functioning executable (.out) for the program. The novice might think to include the implementation file:
#include <iostream>
#include "strextra.h"
#include "strextra.cpp"
That's an evil and terribly wrong solution! Never include a C++ source file directly!
To fix this problem, what we want to do is tell the compiler where those definitions are at. We do this at compile time. If your main program were called myprog.cpp, you'd normally compile like this:
$ CPP myprog myprog.cpp*** $ _
If this program used the strextra library discussed above, you would now have to compile it like this:
$ CPP myprog strextra myprog.cpp*** strextra.cpp... $ _
This tells the compiler to compile both .cpp files and then combine them into a single executable.
For a complete example, see the sample libraries provided elsewhere on these pages.
To test a library, you need to write a driver program. A driver must call each function from the library (use each facility — don't forget those constants and typedefinitions) at least once. A common pattern is to read the function's inputs from the 'user' (a programmer testing the library in this case), pass those values in, retrieve the output(s), print the output(s) for the 'user' to verify, looping as desired:
do { // read test inputs // call function // print result(s) cout << "Test again? "; cin >> ans; } while (toupper(ans) == 'Y');
And do this for each function that needs testing.
Of course, testing an entire library when you've just added a single function to the end can sometimes be tedious. You might use a primed testing loop to fix this:
cout << "Test function ___? "; cin >> ans; while (toupper(ans) == 'Y') { // read test inputs // call function // print result(s) cout << "Test again? "; cin >> ans; }
Note how the initialization question and the update question aren't the same, but they have the same 'sense' — that is, both continue the loop on a positive response.
The truly lazy would even work out a menu for their driver so they wouldn't have to answer n to all the other functions before getting to test their new one. They could just choose it directly from the menu and test it and then quit.
inline functions require that their definition be seen before a call. Because of this, they cannot be effectively prototyped. And because of both of these facts, inline functions are defined in a library's interface file — not in the implementation like normal functions.
Because the standard requires that default arguments' default values only be seen on the first head that it encounters, such values are on the prototype of a normal function or the definition of an inline function. Either way, they will be in the library's interface file — not its implementation.
When you need to use a constant (say, for a default value) or a function (called from within an inline function, perhaps) that come from the standard libraries, you must #include such libraries' interface files. Normally, this would also trigger you to use the using namespace std; statement to make access to these identifiers easier. However, when these things happen in your library's interface file, you cannot use this statement! If you did, you'd be forcing anyone who #included your library to use the standard namespace, too. Not nice.
Instead, you'll have to use the alternative syntax of placing 'std::' in front of every standard library identifier that occurs in your library's interface file. I.E.
#include <cmath> const double PI = std::atan2(0.0, -1.0); inline double radians_to_degrees(double radians) { return radians*180/PI; } inline double degrees_to_radians(double degrees) { return degrees*PI/180; } inline double deg_sin(double degrees) { return std::sin(degrees_to_radians(degrees)); }
(Please note that the ALL_CAPS constants from the old C libraries can not be preceded by a 'std::'. This will be true of most if not all standard library constants that were inherited from C, in fact. constants from C++ standard libraries are typically found inside the namespace std...or even inside a class type inside of the std namespace — std::string::npos, for instance.)
Or:
#include <iostream> inline void print_money(double cash, char symb = '$', bool front = true) { if (front) { std::cout << symb; } std::cout << cash; if (!front) { std::cout << symb; } return; }