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:
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:
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 ashared_ptr<ObjectBase>
, and you do not need to use the->
operator. This also ensures that when theObject
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).
Beautiful simple API
The result of all of the above is a beautiful and simple API. It should be possible to simply write:
Object object{"A"}; // --> Use implementation `ObjectA` object.method();
void use_object( Object& obj ) { obj.method(); }
Object
instance is not a deep-copy, but rather only a copy of the internal
(shared) pointer. Therefore the pass-by-reference (&
) in the last snippet is not strictly necessary,
but however a very small performance optimization: no reference counting needs updating in the shared_ptr
.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: