BEST practises

SolAR guidelines

The following rules shall be used for every addition/modification to the SolAR project. This encompasses the SolAR framework and the GUI interface and unless otherwise specified, these rules shall apply to both.

Component Interfaces

1.1. Solar component interfaces are virtual base classes. They shall inherit from class org::bcom::xpcf::IComponentIntrospect

1.2. SolAR Interfaces are defined in a dedicated header file (.h) whose name shall begin with a capital I followed with the name of the abstract class it refers to, e.g. ICamera.h.

1.3. SolAR Interfaces shall not contain member variables, it is an abstract class without committing to a particular implementation of the class. If you need member variables, declare them in the implementation of the component

1.4. A component interface must be a abstract class, meaning that all its methods must be virtual.

1.5. Solar Framework is organized hierarchically via dedicated directories and namespaces. Currently, concerning the interfaces, this organization is as follows :

Directory namespace

SolARFramework/interfaces/api/display

SolAR::api::display

SolARFramework/interfaces/api/features

SolAR::api::features

SolARFramework/interfaces/api/fusion

SolAR::api::fusion

SolARFramework/interfaces/api/geom

SolAR::api::geom

SolARFramework/interfaces/api/image

SolAR::api::image

SolARFramework/interfaces/api/input/devices

SolAR::api::input::devices

SolARFramework/interfaces/api/input/files

SolAR::api::input::files

SolARFramework/interfaces/api/loop

SolAR::api::input::loop

SolARFramework/interfaces/api/output/files

SolAR::api::input::output::files

SolARFramework/interfaces/api/pipeline

SolAR::api::input::pipeline

SolARFramework/interfaces/api/pointcloud

SolAR::api::input::pointcloud

SolARFramework/interfaces/api/reloc

SolAR::api::input::reloc

SolARFramework/interfaces/api/segm

SolAR::api::input::segm

SolARFramework/interfaces/api/sink

SolAR::api::sink

SolARFramework/interfaces/api/slam

SolAR::api::slam

SolARFramework/interfaces/api/solver/map

SolAR::api::solver::map

SolARFramework/interfaces/api/solver/pose

SolAR::api::solver::pose

SolARFramework/interfaces/api/source

SolAR::api::solver::source

SolARFramework/interfaces/api/tracking

SolAR::api::solver::tracking

1.6. Namespaces should use lower case.

1.7. Any new interface must fall into one of these categories. Yet, if needed, one may ask the SolAR Team to add a new one to fulfill a particular need not covered by the current organization.

1.8. If possible, an abstract interface of a component must define only one processing method. Exception may be allowed if your processing method need take as an input or output only one or a collection of several objects, as for instance:

virtual void drawCircle(SRef<Point2Df> point, unsigned int radius, int thickness, std::vector<unsigned int> & bgrValues, SRef<Image> displayImage) = 0;
virtual void drawCircles(std::vector<SRef<Point2Df>>& points, unsigned int radius, int thickness, SRef<Image> displayImage) = 0;

or if the processing method can take as input our output parameter an object or an inherited object, as for instance:

virtual void drawCircles(std::vector<SRef<Point2Df>>& points, unsigned int radius, int thickness, SRef<Image> displayImage) = 0;
virtual void drawCircles(std::vector<SRef<Keypoint>>& keypoints, unsigned int radius, int thickness, SRef<Image> displayImage) = 0;

1.9. A 128-bit UUID (Universal Unique IDentifier) shall be associated to any virtual interface and explicitly quoted in the interface header file, preferably after the class definition.

The syntax is the following :

XPCF_DEFINE_INTERFACE_TRAITS(SolARnamespaces::IInterfaceClassName,
                             "aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee",
                             "Interface name",
                             "Interface description");

An example :

XPCF_DEFINE_INTERFACE_TRAITS(SolAR::api::display::I2DOverlay,
                             "62b8b0b5-9344-40e6-a288-e609eb3ff0f1",
                             "I2DOverlay",
                             "SolAR::I2DOverlay interface");

1.10. The header file shall contain Doxygen documentation code in order to automatically generate the interface description web page during the continuous integration process used by the Solar Integration Team.
In particular the purpose of the interface shall be documented, as well as each virtual method with its input/ouput parameters.
For instance for class documentation:

/**
 * @class I2DOverlay
 * @brief Drawing interface to overlay 2D information on top of an image.
 *
 * This class provides drawing methods to overlay 2D debug informations on top of an image.
 */

and for method documentation:

/// @brief Draw a Squared Binary Pattern.
/// @param[in] pattern The squared binary pattern to display.
/// @param[in,out] displayImage The image on which the squared binary pattern will be drawn (on the whole image).
virtual void drawSBPattern (SRef<SquaredBinaryPattern> pattern, SRef<Image> displayImage) = 0;

Modules and Components

Modules are the placeholders for the components. Basically, they are defined to reflect a particular type of implementation, based e.g. on a particular technology or a particular provider, etc …​

A component cannot exist by itself. It must be included in a module but a module may contain only one component if needed.

They are are few rules that modules and components must conform to in order to be usable by SolAR. This is explained in the following.

  • Modules are delivered as shared libraries (windows or linux)

  • The recommended naming convention is ModuleName, where name should reflect a characteristic of the module, e.g. ModuleOpencvFree.

  • Components are declared inside a namespace according to the following naming convention : SolAR::MODULES::NAMEOFMODULE

  • A 128-bit UUID (Universal Unique IDentifier) shall be associated to every module

  • A 128-bit UUID (Universal Unique IDentifier) shall be associated to every component included in a module

  • A Module exports its components via a dedicated Export API defined in a header file named NameOfModuleAPI.h, such :

/**
 * Copyright and license notice ......
 */

#ifndef NAME_API_H
    #define NAMEOFMODULE_API_H
    #if _WIN32
        #ifdef NameOfModule_API_DLLEXPORT
            #define NAMEOFMODULE_EXPORT_API __declspec(dllexport)
        #else //NameOfModule_API_DLLEXPORT
            #define NAMEOFMODULE_EXPORT_API __declspec(dllimport)
        #endif //NameOfModule_API_DLLEXPORT
    #else //_WIN32
        #define NAMEOFMODULE_EXPORT_API
    #endif //_WIN32
    #include "NameOfModule_traits.h"
#endif //NAMEOFMODULE_API_H

and where 'NameOfModule_traits.h' exposes the list of components contained in the module. Follows a generic example of this file.

#define NAMEOFMODULE_TRAITS_H

#include "xpcf/component/ComponentTraits.h"

namespace SolAR {
namespace MODULES {
namespace NAME {
class Component1;
class Component2;
}
}
}

XPCF_DEFINE_COMPONENT_TRAITS(SolAR::MODULES::NAME::Component1,
                             "aaaaaaaa-bbbb-cccc-dddd-eeeeeeee",
                             "Component1",
                             "SolAR::MODULES::NAME::Component1 definition")


XPCF_DEFINE_COMPONENT_TRAITS(SolAR::MODULES::NAME::Component1,
                             "ffffffff-gggg-hhhh-iiii-jjjjjjjj",
                             "Component2",
                              "SolAR::MODULES::NAME::Component2 definition")


#endif // NAMEOFMODULE_TRAITS_H
  • There will be a code file named NameOfModule.cpp that shall contain the Module UUID as well as which components are included and exposed in the module. The syntax is as follows.
    NameOfModule.cpp:

#include "xpcf/module/ModuleFactory.h"

#include "component1.h"
#include "component2.h"

namespace xpcf=org::bcom::xpcf;

XPCF_DECLARE_MODULE("kkkkkkkk-llll-mmmm-nnnn-oooooooo", "ModuleName", "ModuleDescription")


extern "C" XPCF_MODULEHOOKS_API xpcf::XPCFErrorCode XPCF_getComponent(const boost::uuids::uuid& componentUUID,SRef<xpcf::IComponentIntrospect>& interfaceRef)
{
     xpcf::XPCFErrorCode errCode = xpcf::XPCFErrorCode::_FAIL;
     errCode = xpcf::tryCreateComponent<SolAR::MODULES::NAME::component1>(componentUUID,interfaceRef);
     if (errCode != xpcf::XPCFErrorCode::_SUCCESS)
     {
         errCode = xpcf::tryCreateComponent<SolAR::MODULES::NAME::component2>(componentUUID,interfaceRef);
     }
    return errCode;
}

XPCF_BEGIN_COMPONENTS_DECLARATION
XPCF_ADD_COMPONENT(SolAR::MODULES::Name::component1)
XPCF_ADD_COMPONENT(SolAR::MODULES::Name::component2)
XPCF_END_COMPONENTS_DECLARATION

In the above mentioned code, a two-component module is considered.

Each component is implemented via a class (.cpp/.h).

Components should be implemented for processing. It should take in parameters and deliver out components and data.

2.1. Data structures should be implemented for the data flow, meaning the data that will be exchanged between components at runtime thanks to the processing methods of the components. To optimize vision pipelines developped thanks to the SolAR Framework, it is of real need to take care to data structure optimization (reduce the memory copy, favour a quick access to data, etc.)

2.2. A data structure must be defined in a namespace SolAR::datastructure

2.3. The header and cpp files must be put under the datastructure directory.

2.4. The header file shall contains Doxygen documentation code in order to automatically generate the data structure description web page during the continuous integration process used by the Solar Integration Team. For instance for class documentation:

/**
  * @class Image
  * @brief Specifies the Image base class.
  *
  * This class provides an image abstraction for SolAR
  */

and for method documentation:

/** @brief  reserves new space depending on the image layers and bitspercomponent infos
  *  @param width: width of the image
  *  @param height: height of the image
  */
  void setSize(uint32_t width, uint32_t height);
Please refer to existing SolAR component interfaces and take them as examples.

Component Implementations

A component implements a low-level vision processing in order to offer much more flexibility. They can be interconnected together to create high-level and real-time vision pipeline (localization, 3D reconstruction, etc.).

That is why a component should take in parameters and deliver out data structures to ease their connection (data flow).

When you intent to create a component, first verify that :

  • a SolAR interface already exists for your kind of components. If so, please use the specified interface. if not, or if you think the existing interfaces do not totally fit your need, please contact the SolAR team.

Please look at the organization of existing modules and components.

3.1. Any component must be embedded in a module. Modules are used to easily publish one or a group of components to the solAR community. If you want to create a new module, please copy the existing structure of SolAR modules.

3.2. Your components should have explicit names (that means, that ideally, we do not need to read documentation to understands what it is for and on which third party it is based). Please refer to existing components for naming examples.

3.3. Your components must inherit from component interfaces defined by the SolARFramework. If no component interface fits your need or if you think an existing interface should be extended, please contact the SolAR team.

3.4. Your components can define their own attributes which should be defined as private. No attributes can be shared by other components. If you want to share data between components, pass them as attributes of the processing method.

3.5. Your components can define their own methods which should be defined as private. The only public methods should be the ones defined by the abstract interfaces.

3.6. Processing methods should not pass control or configuration parameters, but only the parameters representing the input and output dataflow of the component. If a parameter does not have vocation to change at each pass of the pipeline, this parameter must be moved as a private attribute of the component.

3.7. All methods defined by the abstract interface you are inheriting must be implemented in your component.

3.8. Your component should inherit from ConfigurableBase if you want to configure it thanks to an external file (please refer to other components, for instance ImageFilter). So you won’t have to recompile your pipeline if you want to change the configuration of its components.

example :

SolARImageFilterBinaryOpencv::SolARImageFilterBinaryOpencv():ConfigurableBase(xpcf::toUUID<SolARImageFilterBinaryOpencv>())
{
    declareInterface<api::image::IImageFilter>(this);
}

3.9. If you want to initialize the value of component attributes at runtime thanks to an external file, you need to wrap it to a naming string. Thus, you will be able to configure your pipeline by editing an external file defining this attribute by its given name. example :

    declareProperty("min", m_min);

3.10. There is no need to implement a setParameter and a getParameter method in a SolAR component. To configure a parameter at runtime in your code, get the wanted property of your component through the IConfigurable interface and set it to the desired value.

example :

auto imageFilterBinary =xpcfComponentManager->resolve<image::IImageFilter>();
imageFilterBinary->bindTo<xpcf::IConfigurable>()->getProperty("min")->setIntegerValue(-1);

3.11. All Dataflow parameters of a processing method that are not modified by the function must be const.

3.12. All Dataflow parameters of a processing method that are SRef and that can be instantiated by the method must be a reference on a SRef, otherwise do not use a reference on a SRef. In the following example, the ouptut image of a ImageConvertor can be auotmatically instantiated according to the size of the input image:

virtual FrameworkReturnCode convert(const SRef<Image> imgSrc, SRef<Image>& imgDst) = 0;

3.13. The namespace of your component must respect the following form: modules::module_name, where module_name refers to the name of your module.

3.14. The namespaces of your component should use lower case.

Please refer to existing SolAR component implementations and take them as examples.
Please refer to existing SolAR components and take them as examples.

3.15. Each parameter of each method of your component should be tested in your code, to detect bad parameter values. When an incorrect value is detected, the method should return an errorcode. This will help developers for implementing and debugging their pose estimation solution.

3.16. Each component should have a corresponding "simple" unit test . The unit test should test component creation, configuration, and simple use of this component, with normal cases. The unit test should be commented, to help other contributors to understand your component.

3.17. Each component should have unit tests with limit cases, as bad instanciation (bad values of attributes), bad use of components (for example: try to load an image that does not exist), and test every error code of each method.

Modules

4.1. A Module is a group of components with the same implementation basis :

  • same third parties

  • same authors

  • same Intellectual Properties

4.2. For each module, there should be an XML file describing the components with their UID. Please refer to SolarModuleTools example, and XPCF documentation to do it.

4.3. In each module, there should be a Module unit test project, gathering the unit tests of all components.

Pipepline Management

SolAR architecture is based on a pipeline manager called XPCF and implemented by b<>com. Please refer to the XPCF github project link: XPCF GITHUB repository to know more about XPCF.

Please be aware that components must be interoperable thanks to XPCF, and that is why thoses guidelines should be used.

Logs to help debugging

A SolARLog tool has been defined in order to help you to debug and test your programs.

SolarLog is based on spdlog, and is managed as a singleton, so that you will have at maximum 2 loggers : 1 console and 1 file.

You have 2 log modes

  • console

  • file

Please initiate your console logger with LOG_ADD_LOG_TO_CONSOLE or file logger thanks to LOG_ADD_LOG_TO_FILE.

You will easily find examples in SolAR sample codes.

Please use one of this macro to log your data, depending on the severity you want :

  1. LOG_TRACE: create a TRACE of INFO level

  2. LOG_INFO(fmt, …​) : create a log of INFO level

  3. LOG_DEBUG(fmt, …​) : create a log of DEBUG level

  4. LOG_CRITICAL(fmt, …​) : create a log of CRITICIAL level

  5. LOG_WARNING(fmt, …​) : create a log of WARNING level

  6. LOG_ERROR(fmt, …​) : create a log of ERROR level

  7. LOG_FLUSH : can be used to force logs flush (console or file mode)

  8. LOG_RELEASE : is used to release the logger (should be used at the end of a program).

coding rules

Project organization

In order to ease the source code management, we should follow the same hierarchy for each module. A module typically becomes one dynamic or static library (dll or lib). To make the code more accessible and friendly for everyone, developers should follow the rules below:

Files

C++/C modules

- Project-wide definitions must be in a dedicated header file (for instance definitions.h)

- Each module may have a common .h file that contains all common constants, macros, enum, structures… It should not contain elements that are not common to classes in the module.

C++/C source files

C++/C source files should contain the implementation of only one class (except for very small private utility classes related to that class). See Naming conventions for naming conventions.

C++/C headers files

C++/C headers files should contain the declaration of only one class (except for very small public utility classes related to that class). See Naming conventions for naming conventions.

Directory layout for each module

The directory layout for each module should be as described in: coding_rules.adoc chapter Project organisation

C / C++ Coding Rules

Why restricting C++?

Even if compilers now correctly compile even the most advanced C++ language features, some advanced features make the code overly complex and difficult to maintain.

Why restricting C?

C can be written in many ways to do the same things but some ways are more obfuscated and offer less robustness.

Then, what language to use?

b<>com is using a mix of C and C++ based on existing code, external dependencies (like platform types, SDK, etc).

That’s why the following rules make sense in our environment and, in order to facilitate porting and code review, developers must use the set of rules defined below. An example of code and header can be found in annexes A1 and A2.

Language features

As it is very easy to make unreadable and non-understandable C code, here are a few rules/restrictions to follow for the C language itself:

Templates

Templates should be used following the "KISS" principle. Extreme template programming must be avoided and replaced with ad-hoc design to ensure code maintainability.

Exceptions

Exceptions must not be used outside package boundary (i.e. outside a static or dynamic library no exception must be thrown).

Operator overload

Operator overloading should be used appropriately.

Weird language features of C++

Weird language features of C++ must not be used, especially:

- static variables that invoke constructors at initialization time (except for some very special cases, such as the singleton pattern)

- run-time type information (‘casts’ can fail at run-time)

Bit fields

Bit fields must not be used for the following reasons: they are not portable because the implementation of bit fields is left to the compiler manufacturer according to the platform; and usage of bit fields is usually inefficient in terms of code size. Use one variable instead of each bit field.

Consider using the STL bitset template class instead.

Namespaces

Namespaces may be used for std classes to avoid the full Class::methName statement. But for internal classes with ambiguous names, try to always use their full class name.

'goto' keyword

The 'goto' keyword should not be used, and if it is, it can only be used to jump to the end of a method for error recovery.

By considering the architecture of a method, this keyword can nearly always be avoided. See annex A3 for examples.

‘continue’ and ‘break’ keywords

The ‘continue’ statement should not be used; the ‘break’ statement should not be used except inside switch statements.

‘return’

The 'return' keyword may be used anywhere in the code.

However, it requires that the no dynamic allocation rule is respected (see below) and that no vital code is skipped.

It also requires that all synchronization is made through C++11 lock_guard objects.

C++ types

Types such as bool, etc. may be used if they are not platform dependant.

C++ iostreams

Iostreams should be used.

Dynamic memory allocations

Dynamic memory allocation should be avoided.

Most of the time, C++ offers semantics that allows no dynamic allocation design.

malloc/free, new/delete should be used during initialization sequence (in the class constructors for instance)

During run time, explicit memory allocations should not be used to avoid memory fragmentation and leaks. If an array is needed at some point during the execution of the program, this need should preferably be planned and reserved at the initialization sequence.

Local arrays are recommended if they are small in size (no more than 16-32 values).

Arrays

use STL’s vector<T> and array<T> instead of old C-style arrays, as C-style arrays don’t behave as expected with C++ objects.

Dynamically allocation of member (aggregated/composed) object

dynamic allocation of a "local" object must occur only when the inner object lifetime is different from the "hosting" class (aggregation case) OR when the used framework doesn’t allow the creation of the object upon class creation (for instance, when no default constructor is available).

When dynamically allocating inner objects, prefer the use of STL’s shared_ptr or unique_ptr (depending on the inner object lifetime), to ensure proper behavior upon exception throwing …​

Class instantiations during run time

All the necessary classes, arrays, structures should be present, allocated and initialized before run time (during the initialization sequence) except for transient objects (objects operated by a pipeline should be created at the beginning and destroyed at the end for instance)

Constants

Constants must be declared using static const or enum for enumeration of constants. #define must be avoided (language evolution tends to avoid #statements).

const

const keyword MUST be used. It must be used appropriately.

Used on method parameters, it clearly shows when a parameter is an input, input/output or output parameter.

Used for methods, it clearly shows that const methods leave the underlying object members unmodified. (typically getters should be const methods).

C++11

Move semantics

Move semantics must not be used. In most cases, move semantics can be replaced with designing the method using C++ references upon output parameters, or with the use of STL shared_ptr.

Lambdas expressions

Threads

C++11 threads and related facilities (mutex, scoped lock_guard, future …​) must be used

Literals

Range for

Range for must be used to work on containers as it improves the code readibility

auto

auto keyword use is recommended when it simplifies the code readibility.

It allows to avoid explicit typing of objects when there’s not a strong interest to :

- for iterators

- for temporary objects

smart pointers

STL’s smart pointers must be used. When possible, it should replace most of old C-style pointers (DLL boundary issue ?)

Function objects

std::function, std::bind, std::mem_fn …​ readibility, maintainability issues ?

STL containers initializer list

Date and time

STL chrono, useful also for performance counters

STL

array<T>, bitset<T>

Libraries and headers

STL containers

STL should be used for container types, such as vectors, lists, maps, etc. (but must not be used across DLL boundaries).

C++ strings

The C++ string object should be used for string manipulation (but must not be used across DLL boundaries).

C++ 'cin', 'cout', 'cerr'

The C++ 'cin', 'cout', 'cerr' must not be used (except inside unit test code and command line tools).

C 'stdin', 'stdout', 'stderr'

The C 'stdin', 'stdout', 'stderr' must not be used (except inside unit test code and command line tools).

C headers/libraries

C headers/libraries may be used.

System specific headers/libraries

System specific headers/libraries must not be used (except in system specific source code – in that case it should be clearly isolated and identified). The code should use as little as possible the windows SDKs (tradeoff between using existing code and code created from scratch).

Multiple header include

To avoid multiple definitions, each header must have:

#ifndef HEADERNAME_H

#define HEADERNAME_H

<header>

#endif // HEADERNAME_H

Include inside header files

#include should not be inside header files in order to avoid include files obfuscation, and to prevent some cases of bad build of a project which shares dependencies with a non-rebuilt project.

Function and variable declaration

Function and variable declarations must be done in header files (and not in other files).

#pragma once

Use of #pragma once is prohibited :

- even if it is supported by a vast majority of c++ compilers, it is not a standard directive of the language

- although it protects from header naming conflict, it doesn’t prevent from ncluding a header twice if it exists in more than one location in a project as files are excluded based on their filesystem-level identity.

Naming conventions

About names

- Words must be in English.

- Words inside the name must start with an uppercase letter. Other letters of the word must be lowercase letters (except for constants).

- Names should not contain underscores '_' (except for constants and the prefixes as specified bellow).

- Names should not contain abbreviations (except if the abbreviation is widely used in the particular field, such as ESDescriptor for “elementary stream descriptor”).

- Names should be explicit according to what they will do, avoid generic names (like i, a, x…).

C++ source files

C++ source files must begin with the name of the class followed by ‘.cpp’.

C source files

C source files must begin with the name of the class followed by ‘.c’.

C++/C headers files

C++/C header files must begin with the name of the class followed by ‘.h’.

C++ template headers files

C++ template header files must begin with the name of the class followed by ‘.[inl|tpl]’.

C++ template source files

C++ template source files must begin with the name of the class followed by ‘.[ipp|tcc]’.

Classes, structures, global functions, structure tags, typedefs, enumerated values

Class names, structure names, global functions, structure tags, typedefs, enumerated values must have their name beginning with an uppercase. Example MatrixBase

Methods

Method names must begin with a lowercase letter (except for constructors and destructors). Example readAccessUnit()

Private members

Private member variable names must be prefixed with 'm_' and start with a lowercase letter. Example m_accessUnitList

Private static members

If used, private static member variable names must be prefixed with 's_' and start with a lowercase. Example s_socketCounter

Local variables

Local variable names must start with a lowercase letter. Example dataLength

Constants

Constants must be all uppercase with each word separated by “_”. Example MAX_LENGTH

Design conventions

Multiple inheritance Polymorphism

Multiple inheritances should not be used, except if the additional classes are pure virtual (equivalent to Java interfaces).

Classes with public virtual methods

Classes with public virtual methods must have a virtual destructor (or else the destructor will not be called). When possible, use the appropriate compiler warning to be warn when destructor isn’t declared virtual while some public methods are.

Static member variables

Static member variables must not be used (these are basically “global variables”). (except for singleton design pattern)

Public member variables

Public member variables must not be used (except in pure “struct-like” classes). Instead, getter and/or setter methods should be provided to access member variables. Example int getMember() { …​.return m_member; }

Error setMember(int variable) { …​.if (variable…​) …​.{ …​…​..m_variable = variable; …​…​..return NoErr; …​.} …​.return Error_NUMBER; }

Unsigned/signed types

Signed and unsigned computations should not be mixed. Signed and unsigned doesn’t work well together and are, in many cases, not comparable one another. Situations like “comparing unsigned values with potentially negative values” or “use signed computations to be casted into unsigned variables” makes the code vulnerable.

Signed types

Unsigned types should be used. Signed types should only be used when the value for the variable or parameter in question could sensibly be negative.

'enum' type

For variables or parameters that may take one of a set of values whose representation is arbitrary, the enum type should be used. Example enum CM_Colors { CM_RED, CM_GREEN, CM_BLUE };

Dynamic length structure

It is recommended to avoid structures with dynamic length. However, if they are used, the size should be bounded in size in order to avoid unlimited memory occupation.

Preprocessor definitions

The definition and use of preprocessing flags (#ifdef/#ifndef) in the source code should be limited; in particular, there should not be any OS or compiler specific code. However, if specific code is present, it should be isolated and clearly identified. Most of the time, a different design approach allows to avoid inlined OS preprocessor definitions (namespace or inheritance usage for instance).

Code under conditional compilation flags

Code under #if, #ifdef, #ifndef should be limited. Theses sections, if not build with the rest of the code, can easily be broken without notice.

Inline

Inline may be used instead of macro for functions that are called often and when they are more than one line long.

Range of variables

Consider the range of each variable: each variable should remain local to a code block as much as possible. Variable like the for iterator can remain local to the loop. If the if condition statement block needs a local variable: declare it inside the statement block. This variable will not be visible outside the block, preventing misuse. Note for Intel compilers: before ICC11, declaring a variable into a for statement for (int myVariable;…) resulted in having the variable defined locally to the function containing the for. With ICC11, this variable exists only with the for statement code block. Example if (myCondFct()) { …​. int myLocalVar = methodVar * m_aMember; …​.useMyLocalVar(myLocalVar); } aMethodThatCanNotUseMyLocalVarHere(); myLocalVar is only used in the if statement block. If someone attempts to use it outside, the project will not build. This variable only serves that code block and it is not useful outside. The code is easier to read, no need to monitor myLocalVar, or wonder if it is used elsewhere…

Scope of variables

Avoid using one variable for multiple purposes (the compiler handles this optimization process better than anybody).

Code organization

It is recommended to differentiate: - Functions dedicated to computing. - Functions dedicated to schedule and control the computing functions. - Functions dedicated to data flow management. Example Error computeFunc(UInt32* res, UInt32* sourceTable) { …​.// compute code …​.res = sourceTable[0] * sourceTable[1] + MY_CONST;

…​.return NoErr; }

Error dataFlowFunc(MyStruct destStruct, MyStruct* sourceStruct) { …​.// copy struct …​.memcpy(destStruct, sourceStruct, sizeof(destStruct));

…​.return NoErr; }

Error controlFunc(MyStruct* destStruct, MyStruct* sourceStruct, UInt32* sourceTable) { …​.Error err; …​.UInt32 res;

…​.err = computeFunc(&res, sourceTable); …​.if (err == NoErr) …​.{ …​…​..err = dataFlowFunc(destStruct, sourceStruct); …​.}

…​.return err; }

Thread concurrency

Use threads with caution. It is recommended to ask architecture experts about the use of threads. Use C++11 threads' library.

Singleton design pattern

This pattern should not be used unless absolutely needed. When used, special care should be taken to consider concurrent access issues; the unique instance should be automatically created in the first call of “getInstance”; and the constructor should be declared as private. Sometimes, a statically created singleton is the prefered choice (more than the dynamically created one).

Casts

Casts should not be used unless absolutely needed. C-style casts must be prohibited and replaced with C++ casts. Example UInt16 var1; UInt32 var2; UInt64 myResult;

myResult = var1 * var2;

myResult = (UInt64) var1 * var2;

myResult = UInt64(var1) * UInt64(var2);

Don’t put two methods calls on the same line. Don’t put break keyword in switch/case statement at the end of a processing line.

Layout conventions

Tabs

Tabs must not be used. Spaces must be used for indentation. Editors should be set to fill with spaces, not tabs. Tab settings tend to be different for editors, printers and web pages. Note: This is obviously an arbitrary choice, but mixing tabs and spaces causes much difficulty in reviewing code…

Indentation

Indentation offset must be set to 4 spaces and is performed according to the following rules: - code surrounded by braces must be indented by one level.

Blank lines

A blank line should be used to separate logically distinct sections of code.

Curly brackets

Compound statements (if, else, else if, while, for, switch, do) must ALWAYS make use of curly brackets, even where the "associated" body only consists of a single line. Structures must use curly brackets around the clause.

Curly brackets

Curly brackets should appear at the beginning of the next line or at the end of the line. Example if (a == b) { …​.c = 0; } else {

}

if (a == b) { …​.c = 0; } else {

}

Parentheses

Although C++ has precedence rules that should ensure a given expression is evaluated in the same order regardless of the compiler, additional parentheses should be used where the order of evaluation is not obvious.

Multiple parentheses

Parentheses on multiple lines must be aligned on the previous parentheses with the same level. Operators must be at the end of the lines. Example if (a == b) && …​..(c == d || …​.(e == f)) if (a == b) && (c == d || …​.(e == f))

Functions

Each function should perform a single well-defined operation. Functions should not be too long. Up to 2 pages of printout or about 100 lines of source code is reasonable. These figures include comments and blank lines.

Source files

Sources files must be small. 1000 lines of source code is reasonable (including comments and blank lines). These files are easier to read and faster to compile (Intel compiler can compile several source files in parallel).

Header files

Header files must be small. 100 lines for headers are reasonable.

Switch

Case/default from a switch statement are written on the same column as the switch keyword. break; and other lines are indented. The break keyword must ALWAYS be on its own line. Mixing the break keyword with processing code makes the code confused : it can be interpreted as "fall-off" code when break is at the end of long lines. Example switch (getStyle(config)) { case STYLE_GOOD: …​.// Ah, it’s so good! …​.break;

case STYLE_BAD: …​.// Oh no, it’s bad! …​.break;

default: …​.// Hmmm! …​.break; }

Instructions

Put one instruction per line.

for

Always put curly brackets in for clause. for instructions must be on their own lines (not on the for line)

Tracing and debugging

Boost::log

Boost::log is the recommended framework to log, as it provides great functionality out of the box without the need for extra/complex configuration

Error handling

Default error codes and types

The default error codes and error types should be declared in a common b<>com header file.

Type of value returned for error codes

The type of value returned for error codes should be Error. Example Error parseString(char *str);

Memory allocation

A method that attempts to allocate memory must provide an allocation failure mechanism, typically by returning an error code. Note that other methods that call such a method must also provide a failure mechanism, and so on…​ Memory allocation should not be performed in constructors as constructors don’t return error code.

File management

The success of a file opening must be checked and if not successful, the error must be handled appropriately. Files must be closed when no longer used or when an error to exit occurs. When closing the file, the return value must be checked.

Function call

The success of a function call must be checked and if not successful, the error must be handled appropriately. The error codes returned by functions must be tested and treated.

Init/deinit functions

After calling constructors and before destructors, it is sometimes necessary to call init and deinit functions to permit error handling on structures that might fail (as these errors cannot be handled in constructors and destructors.

Miscellaneous conventions

Compiler warnings

Source code must not have any warnings when compiled on any targeted platform with any targeted compiler (with a reasonably high warning level – at least level 3).

C – C++ interfacing

All C public interfaces (*.h) which may be compiled with a C++ compiler must wrap the contents of the file with the pair of macros BEGIN_EXTERN_C and END_EXTERN_C. Example BEGIN_EXTERN_C

END_EXTERN_C

C++ interfacing

All C class headers (*.h) which may be compiled with a C compiler must include a C API and ensure the non visibility of C code by putting it within an “#ifdef __cplusplus … #endif” statement.

Dynamic library export

The definition of each class or function that is exported in a dynamic library must be preceded by the XX_EXTERN keyword, XX being the prefix for the module to which the class belongs.

Portability

See http://www.mozilla.org/hacking/portable-cpp.html for more miscellaneous recommendations on portability on various platforms. If a rule differs from b<>com coding rules, follow the b<>com coding rule.

Documentation

Copyright

Each b<>com source and header file must use the template copyright header comment. (See Annex A1: .h) Source from other origins (Open Source for example) may have their own license. In this case, the license must be respected. The headers of third party files must be left intact (then it should not be replaced by b<>com copyright).

Primary documentation of a class

The primary documentation of a class must appear in the header file.

Class description

Each class must have a description before the class declaration. (see Annex A1: class description)

Methods

Each method (public, protected and private) must have a short description before the method declaration. (see Annex A1: setup method description) A method that is already sufficiently documented in the superclass may omit the description or have a single-line comment '// see superclass' (see Annex A1: clone method description)

Member variable

Each member variable must have a description either before the member variable declaration or on the same line. (see Annex A1: member description)

Comments

Comments are written in English. Do not use accented characters in source files. All comments should be “Doxygen” compatible (see Tools). All tags must start with ‘@’ and not ‘\’. Each block of code should be commented. Algorithms must be commented. Bugs from Bugzilla must not be referenced in the code.

/*…/

This type of comment block must be used for comments that apply either to a class, a function, a structure, an enum, a member… which is present below the comment block. (see Annex A1: class description)

//

This comment line should be used inside the code to comment lines in C++ sources. They should be used even for block of comments. (see Annex A2)

//

This comment block should not be used for C++ except for the template copyright block on top of the file and for method and variable documentation.

C/C++ Performance rules

Because we need performance for all code types to achieve close-to-realtime target, these rules replace corresponding rules in previous chapters in order to ensure better software performance. For C++, to improve performance, classes must act as evolved structures/handlers. They must point at a set of non reentrant methods (avoiding static code, allowing parallelism, allowing instantiation).

'goto' keyword

The 'goto' keyword must not be used.

‘continue’ and ‘break’ keywords

The ‘continue’ statement must not be used; the ‘break’ statement must not be used outside of switch statements.

C++ → C convertibility

C++ source must always be convertible into C code. If the rules associated with classes are followed, a class can be immediately converted into a structure and a bunch of methods with, as parameter, a handle on the structure that represents the former members.

Recursive code

Recursive code must not be used for performance reasons and lack of control over the code and because no parallelization and optimization are possible.

No class as class member

A class must not contain another class as a member except through pointers.

Structures must not contain arrays

A structure (class or struct) must not contain arrays except through pointers. The size of the structures must remain reasonable.

Dynamic memory allocation

Dynamic memory allocation must not be used. malloc/free, new/delete must be used during initialization sequence (into the class creators for instance) During run time, explicit memory allocation must not be used to avoid memory fragmentation and leaks. If an array is needed at some point during the execution of the program, this need must be planned and reserved at the initialization sequence. Local arrays are tolerated if they are small in size (no more than 16-32 values).

Class instantiations during run time

All the necessary classes, arrays, structures must be present, allocated and initialized before run time (during the initialization sequence).

Dynamic length structure

Dynamic length structures must not be used (in order to avoid unlimited memory occupation).

Tools

Uncrustify

Most of the code formatting rules described in this document can be enforced using “uncrustify”.

Doxygen

Doxygen extract comments from the source code and generates documentation. It is recommended to check the comment structure with this tool. Refer to the online manual (http://www.stack.nl/~dimitri/doxygen/index.html) for a complete description of DOxygen rules.

Appendix A. Rules management

I. Responsibility

The Development group manager is responsible of these rules.

II. Deviation

Any b<>com source code must follow these rules. Third party packages follow their own rules and should not be modified to follow these rules.

III. Training

Any C/C++ developers and integrators must be trained to these rules.

IV. Control

The compliancy with these coding rules can be performed with Uncrustify tool with the appropriate config file (see tools).