OpenMS
CPP Guide

The following page contains general C++ guidelines that are not OpenMS-specific.

The using directive

Using namespace OpenMS, namespace std, or similar in .h files may cause name clashes, therefore it is advised against.

Warning
Don't import complete namespaces to the scope into .cpp files. Instead, introduce individual symbols to the scope where you need them. For example, write using std::vector; instead of using namespace std;. This immediately gives a hint to where the symbol is defined as well.

UInt vs Size

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:

// OpenMS/include/CONCEPT/Types.h
typedef size_t Size;
size_t Size
Size type e.g. used as variable which can hold result of size()
Definition: Types.h:101

Here is an example of how to correctly use Size.

std::vector<String> myVec;
myVec.push_back("hello");
for (Size i=0; i<myVec.size(); ++i)
{
std::cout << "Index: " << i << " Value: " << myVec[i] << std::endl;
}
Warning
Don't use 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 type (e.g. when comparing length of vectors), use SignedSize (also defined in types.h)

Use SignedSize if you require loop variables with negative values. Here is an example:

std::vector<String> myVec;
myVec.push_back("hello");
for (SignedSize i=0; i<(SignedSize)myVec.size() - 1; ++i)
// never use Size here, because if myVec.size()==0 then Size x = 0 - 1; gives some really BIG number!
{
std::cout << "Index: " << i << " Value: " << myVec[i] << std::endl;
}
ptrdiff_t SignedSize
Signed Size type e.g. used as pointer difference.
Definition: Types.h:108

Math functions

Warning
Don't use GCC to access common math functions like trunc(), round(), log2(), etc. As this is not a C++ Standard requirement, Microsoft have decided to not include them. If GCC is used, it will break the windows port of OpenMS.

Instead, do the following to use these common math functions:

  • ceil() and floor()

    To use these functions, use the following directive:

    #include cmath

    Find information about math functions available at the cplusplus website or for VisualStudio specific problems: MSDN.

  • round()

    OpenMS provides a Math::round() function for convenience (see MATH/MISC/MathFunctions.h).

  • isnan() and isinf()

    Use the boost library. Include:

    #include <boost/math/special_functions/fpclassify.hpp>

    Then use boost::math::isinf(myNumber) and boost::math::isnan(myNumber).

  • log()

    Windows does not support log2(); use log(x)/log(2) instead.

Pass-by-value versus pass-by-reference

Except of primitive types (int, double, float, ....) all method arguments should be passed as non-mutable references.

Return types of methods should be non-mutable references as well, where possible. Sometimes, references can't be used as the retuned value is constructed in the method. If the constructed type is large, save computation time with:

//Bad idea
LargeObject someFunction()
{
LargeObject tmp = ...
return tmp;
}
//Better idea
void someFunction(LargeObject& obj)
{
obj = ...
}

What is OPENMS_DLLAPI?

OPENMS_DLLAPI is a preprocessor macro and ensures that Visual Studio exports this class into the DLL when building the DLL or references the DLL when building an executable.

The OPENMS_DLLAPI macro is defined empty on other platforms, but it might still confuse the syntax parsing of the text editor or IDE. If you are using the Eclipse Platform, fix this at: Project > Properties > C/C++ Include Paths and Symbols.

When to use OPENMS_DLLAPI

When you've written a new OpenMS class, which is not a template class, insert the macro into the header like this:

class Myclass
{ ...

becomes:

class OPENMS_DLLAPI Myclass
{ ...

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:

// Adduct.h
class OPENMS_DLLAPI Adduct
{
...
friend OPENMS_DLLAPI std::ostream& operator << (std::ostream& os, const Adduct& a);
...
}
// Adduct.C
namespace OpenMS
{
OPENMS_DLLAPI std::ostream& operator << (std::ostream& os, const Adduct& a)
{
...
}
}
Main OpenMS namespace.
Definition: FeatureDeconvolution.h:22
std::ostream & operator<<(std::ostream &os, const AccurateMassSearchResult &amsr)

If you forget the OPENMS_DLLAPI keyword, the DLL will have missing symbols and executables might not be able to link against the DLL. When compiled with gcc you will get .. undefined reference to .. errors.

Pointers vs references

Avoid using pointers. Pointers tend to cause segmentation faults. Try to use references instead.

Iterators

In simple looping constructs, iterators are generally preferable to indexed access. Prefer ++i to i++, because the preincrement operator can save a copy constructor. Use const_iterators where possible to help avoid unwanted side effects.

Includes

includes in header files should be avoided and replaced by forward declarations. Unnecessary includes cause longer compile times after changes in OpenMS header.

Reasons for includes in header files are:

  • Headers of base classes have to be included in the header of the derived classes.
  • If a class has members of type T (not T* or T&) the header has to be included.
  • Headers of template classes have to be included.

An example class could look like this:

#include <QtGui/QMainWindow>
#include <QtGui/QPainter>
// Forward declaration in main namespace
class QLabel;
namespace OpenMS
{
// Forward declaration in OpenMS namespace
class Spectrum1DWidget;
class Dummy
: public QMainWindow
{
...
protected:
Spectrum1DWidget* parent_;
QLabel* label_;
QPainter painter_;
}
}
Note
In OpenMS, Qt headers have to be included with the library prefix.

Input/Output

Code like std::cout << "example" << std::endl; forces the output buffer to be flushed, i.e. written to disk immediately, which is not ideal. Get used to writing code like std::cout << "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).