OpenMS
|
The following page contains OpenMS-specific C++ guidelines, which are unrelated to Coding Conventions.
OPENMS_DLLAPI
is a preprocessor macro and ensures that the compiler, e.g. Visual Studio or g++, exports this class into the DLL
when building the DLL
or, in the other case, references the DLL
when building an executable.
The OPENMS_DLLAPI
macro is defined in OpenMSConfig.h, which in turn is created at configure time (when CMake runs).
Details**: on MSVC, its either set to __declspec(dllexport)
or __declspec(dllimport)
, depending on who includes the header (within OpenMS library, or from outside, e.g. TOPP tools or class tests.). On g++/clang it's always __attribute__((visibility("default")))
.
When you've written a new OpenMS class, which is not a template class, insert the macro into the header like this:
becomes:
It is enough to prefix the class with the macro. Do not prefix the members or member functions.
OPENMS_DLLAPI
is also required for structs, global (including extern
) variables and global functions, as long as they are not templates. Never prefix templates with OPENMS_DLLAPI
. The only exception to this rule is when a template is fully specialized (i.e. it can be instantiated). Additionally, prefix nested public structs/classes with OPENMS_DLLAPI
, otherwise you cannot use them from outside the library.
A prominent global function is "operator <<", which is overloaded quite often for OpenMS classes. Unless it is templatized, prefix it with OPENMS_DLLAPI
. If the operator is declared a friend of some class, also make sure the friend statement contains the OPENMS_DLLAPI
keyword. Otherwise, you will get inconsistent DLL-linkage. For example, use:
If you forget the OPENMS_DLLAPI
keyword, the .dll/.so will have missing symbols and executables might not be able to link against it. When compiled with g++
you will get .. undefined reference to ..
errors.
To make direct output to std::out
and std::err
more consistent, OpenMS provides several low-level macros:
which should be used instead of the less descriptive std::out
and std::err
streams. Furthermore, the OpenMS loggers insert console coloring for their output and have a deduplication cache build in, which prevents repetitive outputs by aggregating and counting their occurence. See the OpenMS::LogStream class for details.
In a similar vein: If you are writing an OpenMS tool, you can also use the ProgressLogger to indicate how many percent of the processing has already been performed:
Example: openms/doc/code_examples/data/Tutorial_Logger.cpp
Logging the Tool Progress
Inspect theTutorial_Logger.cpp
for a full example.
Code like stream_object << "example" << std::endl;
forces the output buffer to be flushed, i.e. written to disk/console immediately, which can be a big performance loss. Get used to writing code like stream_object << "example\n";
. Debugging output can be an exception, because the content of the stream buffer may be lost upon segfault etc..
Write many digits to avoid unnecessary rounding errors. In particular, using standard output stream operators, i.e. << for doubles and floats should be avoided when full precision is required because by default, not all significant digits will be written. Before you start using os.precision(writtenDigits(FloatingPointType()));
and alike, it is strongly advised to convert to an OpenMS::String
, i.e. os << String(my_number)
because it's faster, and gives you all significant digits for each type (6 digits for float
, 15 for double
). Similarly, input stream operators are also slow, especially in VisualStudio, so switching to OpenMS::String::toDouble()
is advised for performance reasons. If you do not need all significant digits, simply invoke String(my_number, full_precision = false)
to get up to only three fractional digits for float
and double
types. For Integer
types, there is no problem with streams, but again: OpenMS::String(int i)
is faster. There is usually no heap allocation overhead for strings because of Small String Optimizations (SSO).
OpenMS uses some custom type definitions for simple arithmetic types, such as UInt
(shorthand for unsigned int
). When working with STL
types (especially vectors), assign the return value of a .size()
operation to the OpenMS type Size
, which is defined as follows:
Here is an example of how to correctly use Size
.
UInt
as a substitute for Size
. Even though UInt
and Size
are equivalent on prominent 32 bit systems, they are usually different types on 64 bit systems, where UInt
is 32 bit, whereas Size
is 64 bit depending on the platform. Using UInt
leads to warnings (at best) and may break your code.Size
is an unsigned type. If you need a signed equivalent, use SignedSize
(also defined in types.h
).
Avoid using pointers. Pointers tend to cause segmentation faults. Try to use references instead.
includes
in header files should be avoided and replaced by forward declarations. Unnecessary includes
cause longer compile times.
Reasons for includes in header files are:
T
(not T*
or T&
) the header has to be included. An example class could look like this: