C++ – Best Practice for saving state in a C++ shared object

clibraries

For developing a C++ dynamically-linked library (i.e. shared object) which may interface with C programs, what is the best practice to save program state across exported function calls?

I as a not-experienced C++ programmer can think of the following methods:

  • Compilation unit level static variables.
  • Instantiate a struct at heap which holds the state and passing back and forth its address in each API call (somewhat like JNI).

The problem with the first approach is that my state variables need some data to be initialized and these data are provided by calling init API (one of exported functions). On the other side, when using module's level static variables, those data aren't available yet when those variables are getting initialized.

Also my problem with the second method is that each API function should be supplied with that pointer and this is a bit cumbersome.

Note that there is another option that static variables are pointers to those state variables and are assigned in that init function (actually state variables are instantiated in init and their address are saved in those static variables). This option is fine, but I would like to not use pointers where possible.

Best Answer

Passing a pointer to a state object around is probably the best solution. Why?

The statefulness of your functions is made explicit and obvious. It is the most general and most extensible solution.

Unfortunately, APIs with a (hidden) global state are fairly common. They tend to make simple things simpler, but often make more difficult things outright impossible.

E.g. imagine a database client library. To access a database, you need to create a connection first. What happens if I want to connect to multiple databases at the same time? If there is a single global connection hidden in the library, this is outright impossible.

Passing an extra parameter around is somewhat annoying. But as long as all the state is grouped into a single object that has to be carried around, it isn't very annoying.

Even if I would decide to keep a hidden global state, I would group that state into a single object so that it becomes easier to manage. Keeping track of multiple related global variables is quite error-prone, checking whether a single global variable has been initialized is much easier. Note that with a hidden global state, you will have to check whether the state is set in each exported function in order to prevent user errors.