C++ Libraries — Part I: Design

Inbal Levi
Nerd For Tech
Published in
5 min readFeb 9, 2021

This blog post goes over the basics of creating a C++ library.

It was originally created as a result of questions by a few people, so I decided to create this brief introduction for developers looking to extend their knowledge on libraries in general, and C++ libraries in particular.

This is Part I of a two part blog post, second part can be found here.

If you’re interested in taking your library creation to the next level, this blog post is for you :)

0. What is a library?

“A library is a curated collection of sources of information and similar resources”

Every software engineer is familiar with the term “library”.

In C++ (and especially in projects based on OOP design) almost every class defines an API (Application Programming Interface). But not every API is a library.

Though the quote above is referring to a physical library, this is actually very true for software libraries as well.

You could think of a physical library which contains publications on different topics. Publications appear in the catalog one by one, but you could also group a few into a collection. A software library can have a single functional area (text parsers) or it could be composed of multiple functional areas — like the standard C++ library.

A software library is meant to supply functionality via a defined API, but there’s more to it — Writing a library may sound easy, but it hides a very complex problem.

Before we begin, let’s define those fundamental terms:

  • The library: Library code, which in order to use, the user will have to have to add `#include “mylib.hpp”` in her code.
  • The user code: The part of the program which imports the library, and calls the utilities.

In the following blog, we’ll use the following (over simplified, not working) code examples:

Library (“header only” version)

User code

Notice that the library can be written as “header only” — `.hpp` file only, or as `.hpp & .cpp` (by splitting the declarations from definitions, and moving the definitions into a `.cpp` file).

Writing code in the header can create small technical differences (which will not be explained here), but the “header only library” term will usually be used when templates are used as a technique to write your library code.

The major difference of using “open” templates in the header file comes from templates’ ability to create implementation “on demand” and this can only be done when the .hpp source is provided to the user code.

(templates library will come up later in this blog, but an extensive explanation on templates techniques is planned to be published in a different blog).

I. How to design a library?

There are a few things to consider when you plan to write a library.

What is the API of the library?

This is the most important decision you need to make.

When you define the API, you define how the users will use the library, and more importantly, you define what should not be changed between different versions of your library.

If you change the API — you create a breaking change in your library, so think carefully before defining it.

A best practice (coming from OOP design) will be to encapsulate all the implementation details, and expose only what is needed to be called by the user.

What are the requirements of the library?

Here you need to think of the usage.

Is your library meant to be used by system developers, by other library writers, or maybe something in between?
Is it a local facility (meant to be used by a specific part of the program, such as writing to the file system), or is it a global facility, relevant to all the program? (for example — logs, or a general wrapper)

What is important for the designated users?

This could be one or more of the following: small code size, fast runtime performance, readability, simplicity, backwards compatibility, etc.
Notice that some of these are in contradiction to the others.

How will the users use it?

This section is separated into two different ones. Though they can be related, it’s important to notice that the two are not tied to each other:

  1. The design composition.
  2. The code composition.

The design composition

How the library facility will be used in the importing code. Two main examples would be:

  1. The user code inherit from the class defined in the library.

2. The user code create an object defined in the library, and use its methodes to gain functionality (it can either be created as is, or held by a different object. here I show the second option).

Choosing between the two options, of course, depends on the desired design.

(*) There’s a third option, which is not OOP oriented. This option is more common in C, or in the standard library (only in certain use cases). You should think carfully before using it, since it will effect your namespace.

3. The user code imports the library containing free functions / free template functions

The code composition

This defines the technical way in which the library can be added to the code.

There are multiple ways to add a library, and they are dependent on the way you package the library.

Notice: In the following section I mention Linux-based operation systems files’ extentions, Windows-based operation systems have different file extentions. (The rest of the technicalities are the same)

Generally speaking, two main ways exist:

  1. Add the library as additional source to the project (such as in `header only` library)
  2. Add the library as an artifact, and link with the user code. (.o / .a / .so)
    This option by itself is split into:
    1. Compile the library into a static library (.o, .a), link with it to embed the library code in the user code (will be added to the main.out binary).
    2. Compile the library into a dynamic library (.so).

Compiling into dynamic library by itself contains two options:

  1. Link the user code (.o) with the .so on dynamic linkage:
    This option means:
    - Both the user code (main.out) and the library code (.so) need to be copied to the target environment.
    - main.out can not be executed without having the .so on the environment.
  2. Link the user code (.o) with the .so on dynamic Plug-in linkage:
    This option means:
    - Both the user code (main.out) and the library code (.so) need to be copied to the target environment.
    - The main.out file can be run, and we can change the library (.so) version without restarting the main.out progress. (only calling the `reload` functionality)
    This option will require the `main.cpp` program to have special code (dlopen) to support this option. (by adding some special functions to allow loading the library)

Choosing which of these options to use depends on the needs.
Section V (in C++ Libraries — Part II: Implementation) presents the pros and cons of each form.

Versioning — how often will the library be updated?

Here you need to take under consideration your users, and plan version management accordingly.

Are they working on an offline system? are they keeping multiple versions or just a single one?

Few things to consider:

  1. The larger your user group, the more likely it will be that you will need to maintain multiple versions.
  2. Depending on your user group, you should decide if your library “is allowed” to break the API between versions.

A common way to describe library versions nowadays is: https://semver.org/

As you can see, designing a new library is far from trivial task. Keep in mind — this is just a preview. Before implementing your library it is recommended that you go deeper into the mentioned topics. In the API section, I also recommend reading about CPO’s (Customization Points).

In C++ Libraries — Part II: Implementation, we will dive into the technical part of creating, packaging and linking with the library.

--

--

Inbal Levi
Nerd For Tech

C++ Enthusiast. ISO WG21 member, and head of Israel C++ mirror committee.