Header-only libraries: Advantages and disadvantages?

Hello,

since I have some classes and libraries, which I use in more than one LB project, I thought of designing them in such a way that they are header-only. This would make it much easier for me to manage my projects.
However, I always hear people say that it is best programming style to divide declarations (to headers) and definitions (to source files).
What are your experiences? What are the main disadvantages of header-only libraries?

Thank you,
Timm

Hello,

Although this is not strictly speaking a LB-related question, it is one which is at the heart of the design in OpenLB. So, here’s my humble opinion on header-only vs. compiled-source projects in C++.

First of all, the language has technical constraints which force you to put some of the code in headers and some of the code in compiled-source files.

In classic non-template C++, the general rule to follow is to put function resp. class declarations into a header file and their definitions into a compiled-source file. If you are uncertain about the exact difference between a declaration and a definition, any introductory book to C++ will help you out, but I personally like Scott Meyer’s didactical approach best. Be aware that putting the definition outside the header is a requirement; if you don’t follow this rule, the code is going to be instantiated multiple times if you include the header in multiple source files, which leads to linker errors. From the linker’s point of view the code has what is called internal linkage, and multiple instances of the same code in the object files are not appropriately handled. You could work around this constraint and force all classes and objects to have external linkage by declaring everything inline. But my personal feeling is that such behavior violates the philosophy of the language, and I wouldn’t recommend it. It is always good to follow mainstream approaches to a language, up to a certain extent at least, to make it easier to share and collaboratively work on code.

With templates, everything works the other way round. Template classes and functions have external linkage. You may include them in as many object files as you wish, and the linker never complains. In this case, it is a requirement to put the code into header files, because the compiler needs to see the whole code when instantiating a template for a specific type. Compilation models for templated code are explained in-depth in Vandevoorde and Josutti’s book.

So technically speaking the situation is easy. Non-template code is split between .h and .cxx (or whatever extension you use) files, whereas template code is fully included in headers. In practice, the decision to make is whether your code should be templated or not. Again, Vandevoorde and Josutti’s book contains an excellent discussion of style-issues related to this question. Examples of libraries written as template code are the C++ standard template library, most of the libraries of the Boost project and, of course, OpenLB.

In practice, I don’t find templates very convenient to use. With templates, code cannot be precompiled (except for the “precompiled-headers” mechanisms of some compilers which rarely provide any real benefit), and compilation time can become quite long. This is time-wasting in practice, especially in periods of rapid development-compilation-testing cycles. But conceptually speaking, templates provide an extremely powerful abstraction mechanism in C++, and it would be a pity not to use them.

An interesting approach, adopted in OpenLB, is to offer the best of both worlds: genericity through template-based code, but rapid development cycles through precompiled templates for frequently used data types. This means that all of your code is in header files, but you also write a few .cpp files in which you explicitly instantiate the templates for some data types. Again, Vandevoorde and Josutti’s book is the place to look in order to better understand this. In OpenLB, you switch from one compilation model to the other through a compilation flag. In generic mode, your code can be compiled for any data type (single or double precision) and for any lattice (D3Q15, D3Q19, …), but compilation is quite slow. In precompiled mode, you can only use double precision and D3Q19, but compilation is slow only the first time you compile. The second (and all subsequent) times, precompiled code is used.

Hello Jonas,

this is a very detailed answer. Thanks a lot. I was aware of some of your arguments, and I now understand the advantages and disadvantages of header-only code and its connections to templates. I agree that templates are a real benefit, and I have written some template code. For the non-template code, I will stick to the common way: header + source.
I have realized that, even if one knows how to write efficient C+±code, getting projects with multiple source files and headers working correctly can take some time.
Jonas, did you have programming background before your PhD? When I started last year, I was virtually a rookie, and I see that C++ is extremely powerful, if one has the time to get to know it in detail.

Timm