'Benefits of header-only libraries
What are the benefits of a header only library and why would you write it that way oppose to putting the implementation into separate file?
Solution 1:[1]
There are situations when a header-only library is the only option, for example when dealing with templates.
Having a header-only library also means you don't have to worry about different platforms where the library might be used. When you separate the implementation, you usually do so to hide implementation details, and distribute the library as a combination of headers and libraries (lib
, dll
's or .so
files). These of course have to be compiled for all different operating systems/versions you offer support.
You could also distribute the implementation files, but that would mean an extra step for the user - compiling your library before using it.
Of course, this applies on a case-by-case basis. For example, header-only libraries sometimes increase code size & compilation times.
Solution 2:[2]
Benefits of header-only library:
- Simplifies the build process. You don't need to build the library, and you don't need to specify the compiled library during the link step of the build. If you do have a compiled library, you will probably want to build multiple versions of it: One compiled with debugging enabled, another with optimization enabled, and possibly yet another stripped of symbols. And maybe even more for a multi-platform system.
Disadvantages of a header-only library:
Bigger object files. Every inline method from the library that is used in some source file will also get a weak symbol, out-of-line definition in the compiled object file for that source file. This slows down the compiler and also slows down the linker. The compiler has to generate all that bloat, and then linker has to filter it out.
Longer compilation. In addition to the bloat problem mentioned above, the compilation will take longer because the headers are inherently larger with a header-only library than a compiled library. Those big headers are going to need to be parsed for each source file that uses the library. Another factor is that those header files in a header-only library have to
#include
headers needed by the inline definitions as well as the headers that would be needed had the library been built as a compiled library.More tangled compilation. You get a lot more dependencies with a header-only library because of those extra
#include
s needed with a header-only library. Change the implementation of some key function in the library and you might well need to recompile the entire project. Make that change in the source file for a compiled library and all you have to do is recompile that one library source file, update the compiled library with that new .o file, and relink the application.Harder for the human to read. Even with the best documentation, users of a library oftentimes have to resort to reading the headers for the library. The headers in a header-only library are filled with implementation details that get in the way of understanding the interface. With a compiled library, all you see is the interface and a brief commentary on what the implementation does, and that's usually all you want. That's really all you should want. You shouldn't have to know implementation details to know how to use the library.
Solution 3:[3]
I know this is an old thread, but nobody has mentioned ABI interfaces or specific compiler issues. So I thought I would.
This is basically based on the concept of you either writing a library with a header to distribute to people or reuse yourself vs having everything in a header. If you are thinking of reusing a header and source files and recompiling these in every project then this doesn't really apply.
Basically if you compile your C++ code and build a library with one compiler then the user tries to use that library with a different compiler or a different version of the same compiler then you may get linker errors or strange runtime behaviour due to binary incompatibility.
For example compiler vendors often change their implementation of the STL between versions. If you have a function in a library that accepts a std::vector then it expects the bytes in that class to be arranged in the way they were arranged when the library was compiled. If, in a new compiler version, the vendor has made efficiency improvements to std::vector then the user's code sees the new class which may have a different structure and passes that new structure into your library. Everything goes downhill from there... This is why it is recommended not to pass STL objects across library boundaries. The same applies to C Run-Time (CRT) types.
While talking about the CRT, your library and the user's source code generally need to be linked against the same CRT. With Visual Studio if you build your library using the Multithreaded CRT, but the user links against the Multithreaded Debug CRT then you will have link problems because your library may not find the symbols it needs. I can't remember which function it was, but for Visual Studio 2015 Microsoft made one CRT function inline. Suddenly it was in the header not the CRT library so libraries that expected to find it at link time no longer could do and this generated link errors. The result was that these libraries needed recompiling with Visual Studio 2015.
You can also get link errors or strange behaviour if you use the Windows API but you build with different Unicode settings to the library user. This is because the Windows API has functions which use either Unicode or ASCII strings and macros/defines which automagically use the correct types based on the project's Unicode settings. If you pass a string across the library boundary that is the wrong type then things break at runtime. Or you may find that the program doesn't link in the first place.
These things are also true for passing objects/types across library boundaries from other third party libraries (e.g an Eigen vector or a GSL matrix). If the 3rd party library changes their header between you compiling your library and your user compiling their code then things will break.
Basically to be safe the only things you can pass across library boundaries are built in types and Plain Old Data (POD). Ideally any POD should be in structs that are defined in your own headers and do not rely on any third party headers.
If you provide a header only library then all the code gets compiled with the same compiler settings and against the same headers so a lot of these problems go away (providing the version of third partly libraries you and your user uses are API compatible).
However there are negatives that have been mentioned above, such as the increased compilation time. Also you may be running a business so you may not want to hand all your source code implementation details to all your users in case one of them steals it.
Solution 4:[4]
The main "benefit" is that it requires you to deliver source code, so you'll end up with error reports on machines and with compilers you've never heard of. When the library is entirely templates, you don't have much choice, but when you have the choice, header only is usually a poor engineering choice. (On the other hand, of course, header only means that you don't have to document any integration procedure.)
Solution 5:[5]
Inlining can be done by Link Time Optimization (LTO)
I'd like to highlight this since it decreases the value of one of the two main advantages of header only libraries: "you need definitions on a header to inline".
A minimal concrete example of this is shown at: Link-time optimization and inline
So you just pass a flag, and inlining can be done across object files without any refactoring work, no need to keep definitions in headers for that anymore, which can slow down your compilation times in build systems that automatically rebuild includers.
LTO might have its own downsides too however: Is there a reason why not to use link-time optimization (LTO)?
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
Solution | Source |
---|---|
Solution 1 | |
Solution 2 | David Hammen |
Solution 3 | Phil Rosenberg |
Solution 4 | James Kanze |
Solution 5 |