Banner: ZumGuy Publications and Network

ZumGuy Publications and Network

Making C++ libraries

Posted by Sean on Monday, 2nd October 2017 21:19
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 drectory 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
!
Posted by Andrew on Wednesday, 4th October 2017 08:53

This will help me a lot. Thank you!

You must be logged in to post messages.

Quote of the day...


ZumGuy Internet Promotions