Welcome the Programmer's Manual of the ALib C++ Class Library!
This manual explains the structure and concepts of the library and it's general use. The library is divided into "modules" which together comprise the actual library functionality. Please note, that for each of these modules a dedicated Programmer's Manual exists, which solely address a module's field of application.
The module's entities reside in inner namespaces of the libraries root namespace alib, which consequently is rather quite empty - apart from tons of type aliases. (The rationale for these aliases is explained here.) The majority of the remaining code in that namespace is related to organize the modules.
It becomes quite obvious that, before talking any further about this library, all about ALib Modules has to be explained.
Being a "general purpose" C++ library, quite different areas of programming are addressed by ALib. This is why the library code and documentation is organized into different "modules".
The rationals for organizing the library in modules are:
ALib distinguishes between normal modules and so called ALib Camps. This term is explained in a later chapter. For now, it is sufficient to know that an ALib Camp is a more "heavy-weighted" entity, compared to a normal module.
The following table lists all non-Camp modules in alphabetical order:
Name (Programmer's Manual) | Namespace (Reference Documentation) | Description |
---|---|---|
BitBuffer | alib::bitbuffer | Provides types to read and write data into a stream of bits in a platform-independent manner. Furthermore different compression algorithms are given. |
Boxing | alib::boxing | Implements "auto-boxing" for C++, known from higher level programming languages. Transparently wraps C++ values of arbitrary type together with run-time type information into very lightweight objects. In other words, this library provides "type-safe void pointers which support virtual function calls", provides type-safe a variadic argument paradigm and much more. |
Characters | alib::characters | Mitigates C++ character type, character pointer and character array hell. |
Containers | alib::containers | ALib container types. Furthermore tools to enable the containers of the C++ Standard Library to use alib allocators are included. |
Enums | alib::enums | Extensions for otherwise limited C++ enum types. |
Monomem | alib::monomem | Monotonic allocation utilities to avoid heap-memory usage. Together with the concepts introduced with module ALib Containers, the amount of potential use cases for monotonic allocation are tremendously extended. Note that the cost of heap allocation is largely underestimated by programmers. |
Singletons | alib::singletons | Implements the singleton paradigm that overcomes the Windows OS DLL boundaries (caused by Window DLL's local data segments), while under other operating systems chooses a faster, straight-forward implementation. |
Strings | alib::strings | With a least intrusive design offers compatible, lightweight, secure, efficient, complete and convenient C++ string classes. |
Threads | alib::threads | Low-level thread primitives. |
ThreadModel | alib::threadmodel | High-level multithreading tools. |
Time | alib::time | A few useful tools for otherwise limited C++ scoped and non-scoped enum types. |
The following table lists all "ALib Camps" in alphabetical order.
Name (Programmer's Manual) | Namespace (Reference Documentation) | Description |
---|---|---|
ALox | alib::lox | A very unique and powerful approach to implement a software's debug and release logging facilities. |
BaseCamp | alib::lang::basecamp | Fundamental types related to the operating system, but also fundamental concepts for the C++ language, like the sophisticated standard ALib throwable type Exception, text formatting tools and other things that found no other home. |
CLI | alib::cli | Implements parsing and partly the processing of command line arguments (CLI stands for "command line interface"). In case of pure CLI software, provides tools for formatted and localized help and result output. |
Configuration | alib::config | Transparently encapsulates access to configuration or other input data coming from various sources, like command line parameters, environment variables, INI-files, 3rd-party configuration databases, etc. Support for persistent write-back of such data is given. |
Expressions | alib::expressions | Expression parser, compiler and a virtual machine for evaluation of compiled expressions. Excels in respect to speed and flexibility and is very easy to adopt in own projects. |
Files | alib::files | Directory and File scanner, optionally filtering with ALib Expressions. |
One feature of ALib is that active support for the use of just a subset of its modules is provided. Now, with the selection of a single module, all modules that the select one depends on, have to be added to that specific reduced library built. For example, module ALib Strings does not compile without the availability of module ALib Characters.
The following diagram shows the mandatory dependencies between the modules:
Besides the mandatory dependencies shown in the graph above, also optional relations between the modules exist. An optional relationship from module A to module B could be phrased as:
"Module A provides more/extended functionality in case that module B is included in the library built.".
These relationships are documented with each module's Programmer's Manual. Furthermore, within the reference documentation, you will find sentences like "This feature is available only if the module ALib XYZ is included in the ALib Distribution."
The following diagram shows the optional dependencies:
If a user wishes to use and compile only a subset of ALib, the fact of having (selectable) modules that are connected through a set of dependencies, has to be taken into account in different ways.
To denote a full or reduced version of ALib, the term ALib Distribution is used.
The selection of ALib library source code to compile (either to a dedicated library file or directly into a custom software unit) depends on the permutation of selected modules. Now, as the modules have dependencies, all sources of all dependent modules have to be (recursively) selected.
ALib comes with a set of CMake scripts that perform such selection. Even if CMake is not used for the daily build process, the scripts may be used once to select the source and header files needed to feed them into the build system in place.
All details about the build process are given in section 6. Building The Library.
ALib header files are designed to be always includable, without the precondition to include other files before. For example, to work with type AString, it is enough to state
#include "alib/strings/astring.hpp"
at the top of the compilation unit in question. Through the recursive header inclusion, the very first header that is processed is always:
This header analyzes the given set of compiler symbols which define the modules included in a distribution. These symbols are named ALIB_MODULENAME and thus enumerate to:
Each symbol that is not given will be defined as either 0
or 1
, by resolving all dependencies of those symbols given.
Selected modules have to be initialized in the right order. "Most independent" have to be initialized first, dependent modules afterwards. All information about bootstrapping the library is provided later in this manual, namely in chapter 4. Bootstrapping ALib.
Excluding module ALib Threads from an ALib Distribution has the special effect of compiling ALib for single-threaded execution. Due to the fact that ALib uses class Lock and sibling types from this module for all its "mutex locks", leaving out this module removes just all concurrency protection from every remaining module!
The advantage of this approach lies of course in performance and code size of single-threaded compilations, because setting a mutex is a redundant action there. It is important to understand that module ALib Threads is mandatory if concurrent access to library functions is performed.
In debug-compilations of the library, if module ALib Threads is omitted, single-threaded use is therefore asserted: An ALib assertion is raised when multi-threading is detected.
To detect multi-threading in debug-code, the underlying threading library (e.g., "pthreads" on GNU/Linux) has to be provided with the build. For this reason, the default CMake-script, which is described in later sections, does not depend on the inclusion of module ALib Threads, but just always searches the target system's thread library. If found, symbol ALIB_EXT_LIB_THREADS_AVAILABLE is passed to the C++ compiler.
The way to prevent searching and adding a thread library is by setting special CMake cached variable ALIB_CMAKE_SKIP_THREAD_LIB_SEARCH to true
.
In general it could be said:
...unless a user knows exactly what she is doing!
There are a few source files and corresponding types in ALib that are not associated with a module, but rather belong to the library in general. The source files are located in subfolder alib/lang
- without its subfolders (!) - and the corresponding C++ types are collected in the library's root namespace alib::lang.
These files are not further organized and have to be included in the build process independent of the selected modules.
Within this manual, the distinct manuals of ALib Modules and within the reference documentation, the term "ALib Distribution" is frequently used. The term addresses the permutation of modules that have been selected from the overall set of modules, at the time of compiling the library. It might be in a user's interest to reduce ALib to a subset of its functionality and this way speed up compilation and decrease library footprint.
The omission of single modules might have "side effects" on other modules in that respect, that while they are still compilable, certain features might be dropped. If this is the case, such dropping is always documented with the feature (function, type, method, member, etc.).
As a sample, ALib Expressions can be compiled with the absence of module ALib Configuration. But in this case functionality for loading and automatically compiling nested expression strings defined in INI-files, command-line arguments, etc. is dropped.
A special treatment is granted to module ALox, by having header file alib/alox.hpp always included in any ALib Distribution. For this reason, the file is placed outside of directory alib/alox, and instead right inside the source's root folder ALib sources.
This allows a user to place ALox log statements in a compilation unit without the need of testing preprocessor symbol ALIB_ALOX or the use of macros IF_ALIB_ALOX and IFNOT_ALIB_ALOX, in the case that her sources should be ready to compile likewise in an ALib Distribution that does not include ALox.
Similar to this, macros ALIB_LOCK and ALIB_LOCK_WITH remain available in the absence of module ALib Threads, as already discussed in previous section 2.4.4 Single Threaded Library Compilation.
After the previous chapter has given detailed information about how ALib is structured into modules and that an ALib Distribution can be reduced to a subset of these modules, it is time to talk about ALib Camps.
In short, an ALib Camp is an ALib Module, which
Both techniques are required by most softwares, but are not well supported by the C++ language. This unfortunately is likewise true for the C++ language feature called "C++ Modules", introduced with language version C++ 20.
Details for both features (bootstrapping and externalized resources) is provided in the next chapter. For now, the following facts and terminology is important to understand:
With this in mind, specific module ALib BaseCamp can be explained. This module resides in source folder alib/lang/basecamp and introduces class lang::Camp and all tool types needed for bootstrapping and resource management.
It would be as simple as this, if we did not add something more to it: We defined this module as a home for classes where we did not think it made much sense to introduce an extra module for. Or, in other words: where we thought: Whenever a user of the library decides to include an ALib Camp in the ALib Distribution, then certain functionality would usually be needed and added likewise.
The module organizes this functionality by introducing inner namespaces in alib::lang. Those are:
In addition, source files found in folder alib/lang/message add types like Exception and Report to namespace alib::lang.
This functionality needs bootstrapping and resources already, and this is why module ALib BaseCamp is an ALib Camp itself. And finally, because this is the lowest and most fundamental of all ALib Camps, its name was chosen to be "BaseCamp"!
Let's recap this chapter quickly:
As just mentioned, module ALib BaseCamp introduces class Camp and every other "high level" module, aka ALib Camp, presents a singleton instance of a derived type. For example, the singleton type of camp ALib Expressions is class ExpressionsCamp and the one of camp ALib CLI is class CliCamp.
These singleton objects - including that of ALib BaseCamp itself - are collected in namespace alib with global instances and are named in capital letters:
This is the basic setup for solving the two challenges: Bootstrapping and Resource Management. Before this manual now dives into the details, one final prerequisite should be given: ALib Camps may (and usually do) share resources and configuration data. Therefore class Camp implements its data members ResourcePool and Configuration as pointers.
The term "bootstrapping" means the one-time initialization of the library that is to be performed in an early stage of a software process. The counterpart of bootstrapping is "shutting down" the library.
Standard bootstrapping and shutdown of ALib is performed by invoking functions alib::Bootstrap and alib::Shutdown. Such invocation usually is performed as one of the first and last actions in function main()
. In source code, this simply looks like this:
The following chapters explain the details behind these invocations, provide recipes to customize certain aspects of the process, and explain how - optionally - the provided mechanics can be leveraged for the bootstrapping and shutdown of custom code entities likewise.
The following circumstances increase the complexity of bootstrapping:
1. The C++ Language:
main()
is invoked.main()
is invoked!), hence also the order of such custom code execution is random.2. Resources And Configuration Data:
4. Module Dependencies:
4. Multithreaded Access:
An ALib Distribution might consist of only "non-Camp" modules. Currently those are: BitBuffer, Boxing, Characters, Containers, Enums, Monomem, Singletons, Strings, Threads and Time,
In this case that such a reduced ALib Distribution is wanted, the snippet of function main()
shown above is all that is needed, because their bootstrapping is done by namespace functions alib::Bootstrap, respectively alib::Shutdown. These two functions are internally changed depending on the ALib Distribution and thus a user of the library does not need to care on the details.
b
Nevertheless, the details are listed here. In fact, only five of the non-camp modules provide any bootstrapping functionality. Each module provides either namespace functions Bootstrap or Shutdown (or both) for that. Those are:
Module | Bootstrap function | Shutdown Function |
---|---|---|
ALib Boxing | alib::boxing::Bootstrap() | (no shutdown necessary/available) |
ALib Enums | alib::enums::Bootstrap() | (no shutdown necessary/available) |
ALib Singletons | (no bootstrap necessary/available) | alib::singletons::Shutdown() |
ALib Time | alib::time::Bootstrap() | alib::time::Shutdown() |
ALib Threads | alib::threads::Bootstrap() | alib::threads::Shutdown() |
More complex boot strapping strategies that are shown in the following chapters, will always under the hood and automatically care for the non-Camp modules. Therefore, a reader might easily forget about them!
When ALib Camps are included in the ALib Distribution, then things can become a little more complex.
But first of all the good news: Even in the usual case that one or more ALib Camps are included in an ALib Distribution, bootstrapping and shutdown of the library is done exactly the same as shown above. In other words:
If the above does not apply to your field of application, all you might need to know is that functions alib::Bootstrap and alib::Shutdown are internally largely extended in the presence of ALib Camps but, it is still the same simple call.
For bootstrapping and shutdown, types derived from class lang::Camp need to implement two protected, abstract methods, namely
Both methods are invoked more than once: Bootstrapping is done in three phases, defined by enumeration BootstrapPhases and shutdown is done in two phases, defined by enumeration ShutdownPhases. The implementations of the methods need to switch upon the given levels and perform different tasks.
Each phase will be finished for all camps, before a subsequent phase is initiated. The loop over the phases and its inner loop over the list of camps is implemented with namespace functions:
The parameterless versions we had seen before are declared in header file alib/alib.hpp, and thus "always" available.
With function Bootstrap, an outer loop over all phases is initiated. An inner loop iterates over all camps, in order of their "dependency level". For each combination of phase and camp, virtual method Camp::bootstrap is called. This ensures that for each phase, "low level camps" are initialized first and those of "higher level" are initialized next.
The three phases are defined as follows:
BootstrapPhases::PrepareResources
All initialization actions that exclusively involve static data, is deemed to be performed in this phase. For example, the registration of box-functions is to be done in this phase.
The phase received its name from the fact that the ResourcePool is already available and a camp is supposed to feed its static default resource strings to the instance retrieved with Camp::GetResourcePool.
More on this topic will be explained in a later chapter.
With function alib::Shutdown(ShutdownPhases targetPhase , an outer loop over the two shutdown phases is initiated. Again, an inner loop iterates over all camps, but this time in reverse order of their "dependency level". For each combination of phase and camp, virtual method Camp::shutdown is called. This ensures that for each phase, "high level camps" are shut down first and those of "lower level" are shutdown later.
The two phases are defined as follows:
For default bootstrapping and shutdown, this is almost all we need to know. The remaining question is: Where is the list of ALib Camps, which is used for the inner loops of functions Bootstrap and Shutdown, defined?
Just like the overloaded, parameterized Bootstrap and Shutdown functions, the list becomes available with the inclusion of camp ALib BaseCamp and with including its header file alib/lang/basecamp/bootstrap.hpp. There, the declaration alib::CAMPS is given, along with function alib::BootstrapAddDefaultCamps, which fills list alib::CAMPS in the right order. The right order means: respecting the dependency hierarchy and permutation of camp basecamp::BaseCamp and those other camps, which might be included in ALib Distribution.
With a complete ALib Distribution, the list will be:
Again, the list is traversed from top to bottom with bootstrapping and in reverse order when the camps are shut down.
Now, if function alib::Bootstrap is invoked without explicitly filling the list beforehand, (right as it was shown in the introductory section of this chapter), then the empty list will be detected and function BootstrapAddDefaultCamps is automatically invoked.
In later sections of this manual, options for modifying these defaults will be demonstrated.
With the knowledge taken from the previous sections, it is now easily understood what was said in the introductory chapter:
Consequently the simple version of bootstrapping was given like this:
int main( int argc, const char **argv ) { alib::Bootstrap(); ... ...
This standard invocation of function Bootstrap not only fills the list alib::CAMPS, but also invokes overloaded function
alib::Bootstrap(BootstrapPhases targetPhase, lang::Camp* targetCamp, int,int,TCompilationFlags).
with parameters:
The loop iterating the phases, starts with the first phase that the camps have not been bootstrapped with, yet, and ends with the given targetPhase. With that, all phases are executed in above sample. And because the target camp is set to the last one in the list, the inner loop covers all camps.
What was not discussed, yet is when the instances of ResourcePool and config::Configuration are created and how the corresponding pointer members Camp::resourcePool and Camp::config are set.
This is how this is done:
The following schematic summarizes this:
With this information the standard process of bootstrapping is well defined. The following chapters introduce different ways to customize bootstrapping.
As explained in previous chapter 4.1 Weak Support For Bootstrapping In C++, the reason why bootstrapping ALib is a non-trivial task, does not lie in specifics of this library, but in a general lack of support for bootstrapping in the C++ language. Any more complex software needs to solve this task.
Consequently, it might be a reasonable decision, to adopt what this library offers and use this concept and the mechanics to bootstrap custom code units likewise.
Doing this is rather simple. The steps are:
main()
function, invoke BootstrapAddDefaultCamps.In more complex scenarios, a software can also create more than one module. Each camp may be appended to list alib::CAMPS (see step 3). If these camps are following a dependency hierarchie, then the lowest (most independent) camps should be attached first. The target camp of the bootstrapping (step 4) is the latest camp added.
This recipe addresses the simple cases. When a software chooses to hook itself into the bootstrap mechanism of ALib as shown above, the resources and configuration data (including for example INI-files) are shared between all modules and thus contain both, data of ALib and that of custom camps.
Often, this is not a desired scenario, because:
How these goals can be reached is explained in the next section.
The default implementation of class ResourcePool used with non-customized bootstrapping is of type LocalResourcePool and this does not externalize the resource strings. To use either alternative, built-in type ConfigResourcePool or a custom implementation of the interface that attaches to a 3rd-party "resource pool backend", all that needs to be done is to create such instance and pass it to method Camp::BootstrapSetResourcePool of the last (!) camp in alib::CAMPS.
This has to be done before the first invocation of function Bootstrap.
As documented with class Configuration, the type allows being equipped with custom mechanics for loading external data. To do so, the bootstrapping process has to be interrupted after the configuration is internally created. This is done by explicitly specifying BootstrapPhases::PrepareConfig with function Bootstrap.
Now, the configuration can be accessed with Camp::GetConfig and is manipulated as wanted. When done, bootstrapping is finalized by invoking Bootstrap a second time with argument BootstrapPhases::Final.
The schematic that shows both approaches at once, then looks as follows:
With the bootstrap process described so far,
To also add a different configuration instances, method BootstrapSetConfig is given, just like the already introduced method Camp::BootstrapSetResourcePool.
Both methods may be called on arbitrary camps, before starting bootstrapping the corresponding phase.
An object set this way will not be overwritten when the corresponding bootstrap phase distributes instances from higher- to lower-level camps. Moreover, if the algorithm finds an object already set, then the distribution loop continues with this object!
This allows a rather natural way to separate all camps into logical sets that share one resource pool and/or configuration.
Nevertheless, to also distribute different configuration objects, the bootstrap phases have to be separated, as shown in the next section.
The fact that method alib::Bootstrap(BootstrapPhases targetPhase.
is allowed to be called with setting parameter targetCamp to a "lower level" camp (one that is not the last of list alib::CAMPS), allows bootstrapping lower-level camps separately from bootstrapping higher-level ones.
As discussed in previous section, such approach might be needed in case that custom camps are integrated into the ALib bootstrap process.
Let's consider the following sample:
main()
function, BootstrapAddDefaultCamps is invoked.The consequences from this approach are:
Alternatively the same can be reached with the following recipe:
We admit, that what was said above is a little complex. But once you understand why both recipes given above lead to the same very same result, you can tell that you understood ALib Bootstrapping!
With this toolset and the knowledge that:
... bootstrapping a software becomes a very well defined and organized undertaking!
Customization of shutting down ALib should be a seldom need and works the same in principle. Everything is done in reverse order. Function alib::Shutdown(ShutdownPhases targetPhase
has defaulted parameters targetPhase and targetCamp and if not specified alle camps in list alib::CAMPS are shutdown over both phases. For this, parameter targetCamp, this time defaults to the first camp in the list.
if targetPhase is specified as Announce then only the first phase is performed and custom actions might be taken before invoking the second phase ShutdownPhases::Destruct.
For simplicity, in contrast to bootstrapping the resource and configuration objects are destroyed right in the reverse loop of phase Destruct, in contrast to adding a post-process second loop and done with bootstrapping. Thus, a "lower" camp's resources and config objects might be already destroyed, at the moment the camp itself is destructed. Hence, phase Announce is the true last point of action in respect to an involvement of these objects.
Due to the facts that
it is important that a compiled ALib library must be incompatible to compilation units that actually use the library. For example, the footprint (size) of library types might be different.
With C++, if incompatible compilation units are mixed, often no warning is issued (neither by the compiler nor the linker) and the execution of a process might lead to very strange behavior which is irritating and hardly to debug.
To detect incompatible compilation settings and library versions, three preprocessor symbols have been defined. Each symbol has a corresponding namespace variable compiled into the library. The symbols and variables are:
Preprocessor Symbol | Namespace Variable |
---|---|
ALIB_VERSION | alib::VERSION |
ALIB_REVISION | alib::REVISION |
ALIB_COMPILATION_FLAGS | alib::COMPILATION_FLAGS |
Along with that, namespace function alib::AssertALibVersionAndFlags is given, which expects comparison values for each of the namespace variables. The little trick here is, that the parameters are defaulted with the macros. Consequently, the parameters must not be given when the function is invoked.
Furthermore, the parameters are also silently (aka defaulted) added to overloaded functions alib::Bootstrap, which internally invokes AssertALibVersionAndFlags().
For this reason, nothing specific has to be done by a user of the library, as the checks are already performed with bootstrapping!
With ALib, switching support for multithreading on and off is not a preprocessor symbol. Instead, the support is dependent on selecting module ALib Threads into the ALib Distribution or not.
If it is not selected, certain macros and function remain to exists: They are just emptied and with that pruned by the preprocessor or optimized out bei the compiler.
This slight breaking of the rule - namely to have entities belonging to a certain module still existing when this module is not available - brings a huge benefit: All other modules and likewise the custom code library users, can be implemented without much code cluttering.
The gaol here is: Protection mechanisms against thread racing conditions should generally be included in any user's project, they are just pruned in case that code used with an ALib Distribution that misses module ALib Threads.
All further information is provided with the dedicated Programmer's Manual of module ALib Threads.
As of today, ALib for C++ is compiled and tested under the following platforms and toolchain combinations:
All development was performed in a) CLion/CMake (all platforms) and b) Visual Studio Community Edition 2022. All necessary IDE files are included in the repository.
Adoptions to other platforms, toolchains, and IDEs should be implementable with limited efforts. All relevant code which selects platform/toolchain specific libraries, intrinsics, etc., will expose a preprocessor error if a section fails due to an unknown environment. This allows quickly inserting the right platform/toolchain specific code at these places.
Especially errors occurring in header alib/lang/integers.hpp might be quite likely for unknown platform / toolchain combinations. Here, a set of five compiler symbols might be passed using the build system (e.g., CMake), which are documented with symbol ALIB_SIZEOF_INTEGER.
The C++ compiler warning level is defaulted to the bearable maximum. This means, that the inclusion of ALib headers into a custom project's compilation process should never lead to compilation warnings or errors when similar high custom warning levels (of the including project) are used.
While project files for different IDEs might be provided with the codebase, the main development of ALib is performed using CMake scripts. Also, the relationship of source file and ALib Module selection is "officially defined" by CMake scripts.
Even for non-experienced users (in respect to CMake), the syntax of the file should be easily understood. So, do not hesitate to open and read the CMake files for your project setup reference.
A Microsoft Visual Studio solution and according project files which build an ALib DLL and the unit tests, are included in the ALib distribution. These may be used to compile an ALib library that includes all of ALib, which can be used for own projects.
Limited library projects that include only a selection of modules of ALib are not provided and thus have to be created if they are desired.
As of today, no installation process of a shared library version of ALib is available with the build process defined. Not installing a library has of course the disadvantage, that every software project needs to compile its own version of it, and the library is by default not shared between applications, even if compiled as a "shared library", respectively DLL.
While this may change in the future, the advantage of this approach is that an application has a lot flexibility in respect to compiling ALib using the exact set of features it desires.
Therefore, to enable a software to use ALib, the sources of the library have to become a part of the build process in any form. As usual, there are three possible basic options:
When this manual section talks about "building the ALib library", one of the three options is meant.
An extensive set of unit tests is included in the distribution.
Clone the ALib repository from ALib at GitHub to a place where you commonly store 3rd party libraries that your projects are using. Alternatively, download and unpack the ZIP file to that same place.
In this documentation, we will refer to this library location as the ALIB_BASE_DIR. After a fresh installation, it should contain at least the following subfolders:
ALIB_BASE_DIR/build ALIB_BASE_DIR/docs ALIB_BASE_DIR/html ALIB_BASE_DIR/src ALIB_BASE_DIR/tools
To build the unit tests, perform the following steps:
ALIB_BASE_DIR/build/cmake/unittests
cmake ..
make -j
The compiled sample executable should have been created and you can start it with
./ALib_UT
Within a few seconds all unit tests should have been performed.
For a release build, the steps are similar. After you cd into the 'release' folder, the cmake command is:
cmake -DCMAKE_BUILD_TYPE=Release ..
With using CMake, compiling and using ALib is very straight forward. This is because a set of easy to use CMake scripts is provided, of which one is to be included into the custom CMake script.
The following demonstrates this step-by-step along the sample project found in folder
ALIB_BASE_DIR/src/samples/alox
Step 1: Creating the CMake file
A custom CMake file has to be created. To start with, the top of the file might look like this:
Step 2: Choose ALib Modules
The list of ALib modules to be included in the built is defined with CMake list variable ALIB_DISTRIBUTION
which has to be set before invoking the ALib CMake script. If the list is left empty, it will be defaulted to "ALL", which chooses all ALib modules available.
In our sample, we add "ALOX", which chooses module ALox and all dependent modules.
Step 3: Set Other Feature Variables
Our project should be compiled using C++ 17. This is set with:
Step 4: Include "ALib.cmake"
Now we are ready to include the main ALib CMake script:
Note that this sample is using a relative path. In a real-world sample, the path might as well be an absolute one. After the script is run, some global CMake variables and functions are defined. All of those are documented in the next chapter. Let us first continue the sample:
Step 5: Define A Library Project
We invoke CMake function ALibAddSharedLibrary
, which creates a CMake target called "ALib_SharedLib"
having all necessary settings:
Step 6: Define The Custom Project
Now we are good to define our custom project in a usual way:
Step 7: Add Compiler And Linker Settings
Our main project needs to share some ALib compiler and linker settings with the ALib library project. To achieve this we invoke CMake function ALibSetCompilerAndLinker
:
Step 8: Add Library Project To Custom Project
The final step is to add the library project to the custom sample project, which is again a standard CMake task:
That's it. With this simple CMake file we have created a tailored ALib Distribution library and have linked it to our sample project, which is now ready to be built!
The previous chapter demonstrated the use of the CMake script ALib.cmake
provided with ALib. In the following sections a reference documentation on all aspects of the script is given.
Along these lines, the build requirements of ALib is explained - also for users of any other build system. independent of the build-system used, the following information has to be collected and accordingly set:
CMake list variable ALIB_DISTRIBUTION
is an input/output list which defines the particular ALib Modules that should be included in the built. The script will process the values given and will extend the list to include all necessary modules that the given selection depends on.
The values correspond to the module names in upper case letters, hence ALOX, BOXING, CHARACTERS, etc...
If the variable is not set or contains special name "ALL", all modules are chosen to be built and included.
The following CMake variables are available after the invocation of the script:
ALIB_BASE_DIR
ALIB_SOURCE_DIR
${ALIB_BASE_DIR}/src
ALIB_SOURCE_FILES
ALIB_INCLUDE_FILES
The script will create a set of cached boolean CMake variables (which are variables that can be edited with CMake GUI tools and various C++ IDEs).
The following variables correspond directly to preprocessor symbols used for code selection and thus this list links to their corresponding documentation:
In addition, the following further cached variables are set:
ALIB_VERSION
Defines the ALib library version. This variable cannot be changed, respectively will be overwritten on CMake generation. It is specified as a cached CMake variable just for the reason of presenting the ALib version to tools that allow to modify CMake settings.
Furthermore, non-cached version variables ALIB_VERSION_NO
and ALIB_VERSION_REV
are set which hold the version number and revision number as separate integral values.
true
, extended information will be printed with running the CMake script. See 6.5.1 Other Build Systems for details.true
, the provided script will not search for a thread library on the target platform. For more information see chapter 2.4.4 Single Threaded Library Compilation.true
, symbols _GLIBCXX_DEBUG, _GLIBCXX_DEBUG_PEDANTIC and _GLIBCPP_CONCEPT_CHECKS are passed to the compiler.true
, option –coverage is added to CMake variables ALIB_COMPILER_OPTIONS and ALIB_LINKER_OPTIONS The script will create the following non-cached CMake variables, which can be used to define build-settings of custom projects:
target_compile_definitions()
. target_compile_options()
.target_compile_options()
. set_target_properties()
. target_link_libraries()
.In addition, the following non-cached variable is an input variable which may be set before invoking the script:
target_compile_features()
.The script will define the following CMake functions:
ALib_StaticLib
. ALib_SharedLib
. POSITION_INDEPENDENT_CODE
is switched on.The library's filename is determined by (non-cached) CMake variable ALIB_LIBRARY_FILENAME
. If this variable is not set before invoking script ALib.cmake
, then the name is determined automatically from the selected modules.
The name is used with functions ALibAddStaticLibrary
and ALibAddSharedLibrary
, introduced in the previous section.
If not provided, the name will be assembled according to the following set of rules:
"alib_"
."2412R0"
."_DBG"
is appended.'_'
) unless a module is not "superseded" by another module that has a mandatory dependency to it."_DBGSTRINGS"
is appended instead of "_STRINGS"
._ALOX
is extended by suffix NRL
if release logging is switched off. In addition, suffix NDL
is added with debug-builds that do not include debug logging. Note that both exception cover the non-default, unusual compilation cases in respect to the availability of debug- and release logging.As already explained, the CMake build process is viewed to be the reference process for ALib compilation.
For noneCMake users, the selection of the exact minimum set of source and header files, is probably the most difficult (and annoying) task. In CMake we have separated the source selection into script:
ALIB_BASE_DIR/build/cmake/ALibSources.cmake
which is invoked by the main script.
This script might be analysed to identify the source and header file dependencies of the different ALib Modules.
Likewise, script
ALIB_BASE_DIR/build/cmake/ALibModules.cmake
might be analysed to get actual (and correct) information about module dependencies.
Furthermore, by setting CMake cache variable ALIB_CMAKE_VERBOSE to true
, running CMake will write extended information that might be used and copied into the configuration files of other build systems or into IDE project files.
Among the data displayed is:
As demonstrated in the chapter A Step-By-Step CMake Sample (Step 3), CMake variable ALIB_COMPILER_FEATURES
may be used to determine the C++ language standard for ALib targets and optionally for custom targets (i.e. if function ALibSetCompilerAndLinker
is invoked for a custom target).
ALib requires C++ language level 17
as a minimum and is compatible with levels 20
and 23
. ALib might perform better with higher language levels.
CMake provides other (maybe even more preferable) mechanics to determine/set the C++ language level, which of course may be used alternatively.
If CMake variable ALIB_SOURCE_COPY_TARGET_DIR
is set before invoking CMake script ALib.cmake
, then the source code (compilation units and header files) are copied to the directory specified in the variable. If the variable does not point to a valid directory, an error is raised and the CMake script is stopped.
The files copied represent exactly the set of files which are needed for compiling the combination of ALib Modules, which are optionally specified with list variable ALIB_DISTRIBUTION
(see 6.4.1 Selecting ALib Modules).
This feature therefore can be used to create a fresh, filtered copy of the ALib source tree tailored to an application. Nevertheless, it is usually not recommended to do so, because source files that are not used by a combination of modules are neither included as header files, nor compiled. The feature is rather used by the ALib developers to verify module and source code dependencies.
ALib has no mandatory dependencies to external (3rd-party) libraries. The following optional dependencies exist:
There is little to say here, so we note just some bullet points:
The C++ language standard does not suggest naming rules. This is different to, for example, with the JAVA
language. While some people could say, that the C++ standard library naming scheme suggests such rules, others say that it is especially valuable, that code outside namespace std
differs from that - to be better distinguishable within source code.
ALib uses the following rules:
As a final remark, when browsing the code it could be noticed that a lot of block-formatting is used. While a line width of 100 characters is usually not exceeded, for the sake of building code blocks, sometimes this line width barrier is ignored. From our perspective, the usefulness of tabbed code blocks is very much underestimated. Besides readability, a lot of annoying typos can be detected easily, before a next build run.
In addition, tabbed code blocks often largely improve the ability to quickly refactor a codebase manually. It should be allowed to mention, that the editor of C++ IDE CLion here has some unrivalled features: Instead of only supporting block selection and copy/paste of blocks, this editor creates a set of completely new "carets" at the moment block selection starts. We want to thank the team of JetBrains for supporting ALib development by providing a free Open Source License of their absolutely superb set of IDEs.
Most of the time, a 3rd party library like ALib is used to help solving a certain task. Such tasks are addressed with higher level modules, like ALox, ALib CLI, or ALib Expressions. In this case, the corresponding module documentation should be read directly, without loosing time on learning too much about the lower level modules and types.
If you find the lower level types useful for your own projects, those will be understood over time and by reading the reference documentation of types found in their corresponding namespaces.
Should you really just be interested in general and therefore absolutely not knowing where to start reading, we recommend to start with some of the module's programming manuals, for example ALib Boxing or ALib Strings. As debug log-output is something most projects needs, another recommendation might be the to check out the tutorial of module ALox.
The manuals mentioned above may provide you with a good grasp of the design principles and features of ALib in general and thus with the value that ALib might bring to your own software projects.
Once a C++ library becomes more complex, their types get split into different namespaces and inner namespaces. This is also true for ALib, which is split into various modules. Consequently, the common need to conveniently address all types is to add several using statements to a compilation unit. With ALib this could, for example, look like this:
using namespace alib::threads; using namespace alib::strings; using namespace alib::expressions;
To avoid the need of the right "permutation" of using statements at the top of a users' compilation unit, ALib "mirrors" all important types into this outer namespace with type definition statements. For example, at the end of header file alib/expressions/compiler.hpp, just before closing outer namespace alib, the following statement is made:
With that, a single:
using namespace alib;
fits for all ALib types and in most cases, this single using statement is all that is needed. This approach does not generate conflicts: If for example the using code would have an own class named "Compiler", it can fall back to the standard schematic, by adding just:
using namespace alib::threads; using namespace alib::strings;
and accessing class Compiler explicitly as alib::expressions::Compiler
.
A
, with this snippet: using namespace A::B; CODE A
, but only those of B
.Most of the type aliases are as simple as sampled with class Compiler above. Also templated types are often just a 1:1 alias, for example:
But sometimes template parameters become fixed. Often (but not always) with that the name changes, i.e., the leading T is removed. As an example, take:
In some few situations, mostly due to technical reasons, template parameters are even changing their position. Of-course, some special care has to be taken if such types are used aliased or not. As an example, take:
Finally, often several different aliases are defined in namespace alib, providing different permutations of template types. For example, type alib::strings;TAString is aliased as:
A core class included in any ALib Distribution is lang::CallerInfo. It stores the following information:
typeid
, usually passing argument *this
.std::thread::id
. This is only included if compiler symbol ALIB_EXT_LIB_THREADS_AVAILABLE evaluates to true
.It is used to collect information about the caller of a function or method member. ALib collects such information mainly on two occasions:
To ease the type's use, several preprocessor macros are defined. To understand their difference, it is important to be aware that, depending on the use-case, caller information is either exclusively collected in debug-builds or collected in any build-type. In the second case, it still should be the user of the library to decide whether source-code information in a release executable or not.
With this background thought in mind, the reference documentation of the following macro list should be enough to fully understand how the data flow is managed and how to efficiently use the macros.
The macros directly relate to class CallerInfo:
The following macros make use of the caller-macros:
This is because they use keyword this
to identify a caller's type. Unfortunately, the C++ language in combination with the preprocessor does not allow automatic detection of the absence of keyword this
. For this reason the core macro ALIB_CALLER has to be redefined before a code unit defines functions and restored back afterward. To do this, header files alib/lang/callerinfo_functions.hpp and alib/lang/callerinfo_methods.hpp are provided. The following example demonstrates how they are to be used:
In C++, templates offer a powerful mechanism for writing generic and reusable code. But using templates extensively in header files leads to longer compilation times and potential code bloat, particularly when templates are instantiated multiple times across different translation units. The problem becomes significant in larger projects where a template may be used with many types or included in multiple source files, forcing the compiler to repeatedly generate code for the same template.
To mitigate these issues, with a few template types, ALib separates
into three different files. These files are:
This approach results in improved compilation speed, code modularity, and manageable build times while still allowing users the flexibility of creating custom instantiations when necessary.
With this recipe in place, it is fairly straight forward how to instantiate your custom version of an ALib-type of that sort. Let's quickly look at a sample.
For class alib::monomem::TMonoAllocator, provided with module ALib Monomem, only one built-in template instantiation is provided. This specifies the single template parameter TAllocator to be of type HeapAllocator. This instantiation is then aliased as alib::MonoAllocator.
The corresponding files are:
Let us look at the compilation unit (the CPP-file):
That is all that the CPP-file needs to contain.
With this in mind, we can now instantiate a custom mono-allocator, one that uses a PoolAllocator as its memory source. This is done in the ALib unit-tests. Here is the excerpt:
With this in place, we can start using the instantiation:
To summarize, the fast track approach when a specific instantiation of an ALib type that splits some method definitions from the type declaration is needed, is to have a look at the compilation unit (the CPP-file), copy its code to an own compilation unit, and replace the given type with your own type.
If this type is needed in different places, create your custom header file and copy the instantiation code and add keyword extern
to turn it into a declaration. This announces that the linker will find the instantiation. Such a header file has to include only the HPP-file that ALib provides, and not the "T.HPP"-file.
For gdb (GNU Debugger), some "pretty printers" are available. Please consult page 1. Pretty Printers for GNU Debugger (gdb).