Design » Object oriented design

Atlas is primarily written in the C++ programming language. The C++ programming language facilitates OO design, and is high performance computing capable.

The latter is due to the support C++ brings for hardware specific instructions. In addition, the high compatibility of C++ with C allows Atlas to make use of specific programming models such as CUDA to support GPU’s, and facilitates the creation of C-Fortran bindings to create generic Fortran interfaces.

Object oriented design in C++

Abstract interface (ObjectBase)

A commonly used feature in Atlas and in object-oriented programming is inheritance and polymorphism. This is used to define a common abstract interface method() in a class ObjectBase, with implementations in concrete classes ObjectA and ObjectB.

An example construction to create a concrete ObjectA in Modern C++ would be:

std::shared_ptr<ObjectBase> object{ new ObjectA( args... ) };

Now algorithms can be created accepting the abstract ObjectBase

void use_object( const ObjectBase& object ) {
    object.method();
}

...

use_object( *object );

Factory with self-registration (ObjectFactory, ObjectBuilder)

In above example the abstract object is hard-coded to be of concrete type ObjectA. You may want to have this configurable depending on a user-defined string object_type. You could then do:

std::shared_ptr<ObjectBase> object;
if( object_type == "A" ) {
    object = std::shared_ptr<ObjectBase>{ new ObjectA( args... ) };
}
if( object_type == "B" ) {
    object = std::shared_ptr<ObjectBase>{ new ObjectB( args... ) };
}

In order to avoid repeating this code in every place this is required, in Atlas we employ a Factory mechanism. with self-registration, so that the above code could be transformed to:

std::shared_ptr<ObjectBase> object = ObjectFactory::build( object_type, args... )

The method ObjectFactory::build() can in principle just wrap the above code, but for reasons of maintainability and more importantly extensibility, Atlas implements this using self-registration and an abstract ObjectBuilder as follows:

cpp_factory.png

All that is now needed to register a concrete ObjectBuilder is to place

static ObjectBuilderT<ObjectA> builder_A{ "A" };
static ObjectBuilderT<ObjectA> builder_B{ "B" };

anywhere in a global scope. A good place would be in the file where each concrete Object is defined. When the code is compiled into a shared library, then these builders are automatically registered in the ObjectFactory when the library is loaded at run-time.

Pointer to abstract implementation (Object)

Another idiom which is adopted in Atlas is the Pointer to implementation (PIMPL) idiom. This means that we create a class Object which contains as only data member a (shared) pointer to the implementation ObjectBase, but also mimics the public interface of ObjectBase but delegates execution to the encapsulated pointer:

cpp_pimpl.png

This certainly adds a maintainance cost to the Atlas core developers, as every public routine in ObjectBase must be reproduced in Object. It however adds several advantages for the user, and user-code:

  • Value semantics. You do not have to handle the raw pointer ObjectBase*, e.g. by creating a shared_ptr<ObjectBase>, and you do not need to use the -> operator. This also ensures that when the Object instance goes out of scope, the internal pointer gets deleted (if it is the only instance of the same shared pointer).
  • Factory builder. The creation of concrete types is embedded in the constructor of Object!
  • A compilation firewall. This is achieved because it is not required to #include <ObjectBase.h> inside Object.h (only a forward declaration suffices as it is a pointer).

Object oriented design in Fortran

With much of the NWP operational software written in Fortran, significant effort in the Atlas design has been devoted to having a Fortran OO Application Programming Interface (API) wrapping the C++ concepts as closely as possible.

The Fortran API mirrors the C++ classes with a Fortran derived type, whose only data member is a raw pointer to an instance of the matching C++ class. The Fortran derived type also contains member functions or subroutines that delegate its implementation to matching member functions of the C++ class instance. Since Fortran does not directly interoperate with C++, C interfaces to the C++ class member functions are created first, and it is these interfaces that the Fortran derived type delegates to. The whole interaction procedure is schematically shown:

Image alt text