Library design: allow user to decide between “header-only” and dynamically linked?

Preliminary note: I am assuming a Windows environment, but this should be easily transferable to other environments.

Your library has to be prepared for four situations:

  1. Used as header-only library
  2. Used as static library
  3. Used as dynamic library (functions are imported)
  4. Built as dynamic library (functions are exported)

So let’s make up four preprocessor defines for those cases: INLINE_LIBRARY, STATIC_LIBRARY, IMPORT_LIBRARY, and EXPORT_LIBRARY (it is just an example; you may want to use some sophisticated naming scheme).
The user has to define one of them, depending on what he/she wants.

Then you can write your headers like this:

// foo.hpp

#if defined(INLINE_LIBRARY)
#define LIBRARY_API inline
#elif defined(STATIC_LIBRARY)
#define LIBRARY_API
#elif defined(EXPORT_LIBRARY)
#define LIBRARY_API __declspec(dllexport)
#elif defined(IMPORT_LIBRARY)
#define LIBRARY_API __declspec(dllimport)
#endif

LIBRARY_API void foo();

#ifdef INLINE_LIBRARY
#include "foo.cpp"
#endif

Your implementation file looks just like usual:

// foo.cpp

#include "foo.hpp"
#include <iostream>

void foo()
{
    std::cout << "foo";
}

If INLINE_LIBRARY is defined, the functions are declared inline and the implementation gets included like a .inl file.

If STATIC_LIBRARY is defined, the functions are declared without any specifier, and the user has to include the .cpp file into his/her build process.

If IMPORT_LIBRARY is defined, the functions are imported, and there isn’t a need for any implementation.

If EXPORT_LIBRARY is defined, the functions are exported and the user has to compile those .cpp files.

Switching between static / import / export is a really common thing, but I’m not sure if adding header-only to the equation is a good thing. Normally, there are good reasons for defining something inline or not to do so.

Personally, I like to put everything into .cpp files unless it really has to be inlined (like templates) or it makes sense performance-wise (very small functions, usually one-liners). This reduces both compile time and – way more important – dependencies.

But if I choose to define something inline, I always put it in separate .inl files, just to keep the header files clean and easy to understand.

Leave a Comment

Hata!: SQLSTATE[HY000] [1045] Access denied for user 'divattrend_liink'@'localhost' (using password: YES)