Compiling C++ libraries

If you use C++, you’ve almost certainly already used a library of some kind. Even the classic “Hello world” program requires one:

#include <iostream> // include the iostream library

int main() {
    std::cout << "Hello world!";
    return 0;
}

But what if we wanted to write our own? This has several advantages. First off, if we have a function (or many) we want to use in several projects, we can wrap it (or them) in a neat library, and use it time and time again, without tedious copy/pasting. Secondly, if you’re working on a big project, it gets messy if you have everything crammed into one .cpp file! We can use libraries to break our functionality up into simpler, more manageable chunks. And last but not least, if we have lots and lots of code, this can take a long time to compile, and this can get wasteful if we’re only working on a small part of our project. A library is precompiled, and can save us lots of development time!

Creating the library source code

Lets create a simple library to wrap one function, called square, which takes two numbers and squared them. First off, we want to create our function file, square.cpp:

//square.cpp

#include "square.h"

double square(double x) {
    return x*x;
}

Pretty straightforward. You can already see that we are including another file, square.h. So we’ll create this too:

// square.h

double square(double);

This is what is called a header file. It contains only the declaration of our function square.
Before we move on to the next step, we want to make a small improvement to our file, with what is called a header guard:

//square.h

#ifndef SQUARE_H
#define SQUARE_H

double square(double);

#endif

This uses preprocessor directives to prevent declaring our functions twice, in case square.h is somehow included twice. What happens is that the code between the lines #ifndef SQUARE_H and #endif is only executed if the preprocessor constant SQUARE_H is undefined. If that is the case, define that constant and declare all our functions (just one in this case) – this way declarations will only happen once, because if square.h is included a second time, SQUARE_H will be defined and the declarations will be skipped.

Now that we have our little library source code ready, let’s place it in a folder called lib/. It could be called anything – in fact it’s not even necessary to create it at all – but this name is a popular choice, and it keeps our files nice and tidy.

Let’s create a file main.cpp outside of lib/ to use our library:

//main.cpp
#include <iostream>
#include "lib/square.h"

int main() {
    std::cout << square(5);
    return 0;
}

As far as the source goes, that’s it! Now we have to compile our files to make an executable.

Compiling a file with the library

If we attempt to simply compile our file main.cpp we will get an error:

$ g++ main.cpp -o main
/tmp/cc0V8dkz.o: In function 'main':
main.cpp:(.text+0x1c): undefined reference to 'square(double)'
collect2: error: ld returned 1 exit status

g++ can’t find the definition of square. What we must do is compile the files separately but without linking them. Linking is the final step in the compilation process, in which all binary files of the various libraries are crammed into one executable. Here’s how:

$ g++ -c main.cpp -o  main.o
$ g++ -c lib/square.cpp -o lib/square.o

These two commands will compile the files main.o and square.o, without linking them. Note that this also prevents the compiler from complaining about square.cpp not having a main function!
Then we need to line the files manually:

$ g++ main.o lib/square.o -o main

Now we can execute our main file with ./main and it will behave as expected!

Making a proper library

But what if we have many files we would like to include? This compilation process takes multiple steps and gets very tedious very quickly. We can group many *.o files into one as follows:

$ ar ruc lib/libsquare.a lib/square.o lib/file2.o [...]
$ ranlib lib/libsquare.a

ranlib will create an index for the library and is not required by all systems. The file must be called lib<myname>.a in order for it to work.

Finally, we can compile our main.cpp file using our library:

$ g++ main.cpp -Ilib -Llib -lsquare -o main

Here, -I specifies the directory where the header file square.h is located, -L the location where the library is located, and -l<something> specifies to look for the library <something>.
Of course, this entire process can be automated with make!