Requirements

Resource Management

The C++ Resource Acquisition is Initialization (RAII) idiom should be used wherever possible to ensure exception safety and prevent resource leaks.

Memory Management

std::unique_ptr, rci::InternallyLocked, and std::shared_ptr are the preferred methods of managing dynamically-allocated memory.

std::unique_ptr has sole ownership of an object and destroys that object when it goes out of scope. However, ownership of the object can be passed from one std::unique_ptr to another.

Like std::unique_ptr, rci::InternallyLocked has sole ownership of an object, but it also ensures serialized access to that object.

std::shared_ptr and std::weak_ptr provide reference-counted memory management. Use them judiciously.

Error Handling

Functions indicate errors either by throwing a std::exception or returning a std::error_condition or bool. Exceptions are preferable because they

  • cannot be ignored,
  • have negligible overhead, and
  • improve readability.

Use exceptions for unexpected errors or very infrequent errors. If possible, throw only standard exceptions, i.e. exceptions from (or derived from) <stdexcept> or <system_error>. Exceptions should include information about the values that caused the error whenever possible. For example,

double Sqrt(double x) {
  if (x < 0.0)
    throw std::out_of_range("Sqrt: out of range: " + std::to_string(x));
  return std::sqrt(x);
}

Use RCI_ENFORCE() and RCI_THROW() macros to throw exceptions, including source file line number information and improving readability. Compare the following code to the previous example.

double Sqrt(double x) {
  RCI_ENFORCE(x >= 0.0)(x);
  return std::sqrt(x);
}

The use of RAII eliminates most try/catch blocks. In the case of no-throw functions that must swallow exceptions, be sure to log the exception.

For "errors" that occur as a part of normal radio operation use std::error_condition or bool instead of throwing an exception. For errors that cannot be represented using any of the standard error categories, create a library-specific category derived from std::error_category.

For very simple errors, functions return true if successful, or false if not.

Exception Safety

All functions shall provide at least basic exception safety. Wikipedia defines basic exception safety as:

Partial execution of failed operations can cause side-effects, but all invariants are preserved and no resources are leaked. Any stored data will contain valid values, even if data has different values now from before the exception.

Functions that provide strong exception safety should be named beginning with "Strong" (or "strong"), to emphasize their promise. Wikipedia defines strong exception safety as:

Operations can fail, but failed operations are guaranteed to have no side effects, so all data retain original values.

Otherwise, functions must promise not to throw exceptions, indicated by noexcept. All destructors must make this guarantee.

Thread Safety

All data that are accessed by more than one thread and may simultaneously be modified by another thread are synchronized with either std::atomic, rci::InternallyLocked, or much less frequently rci::ExternallyLocked. The explicit use of std::mutex is usually not necessary.

Locks are organized in a hierarchy, such that higher-level objects may call lower-level objects while holding a lock, but not the other way around. This practice prevents deadlock by ensuring that simultaneous locks are always acquired in the same order.

Special attention must be paid to locking and callbacks. A callback exists whenever a lower-level object calls a higher-level object.

Directory Structure

Libraries have the following structure.

MyLib                     Library directory
├── README.mmd            Overview
├── MyLib                 Interface
│   ├── MyLib.h           Library-specific types, globals, etc.
│   ├── Pimpl.h           Example pointer-to-implementation class
│   └── Class.h           Example "naked" class
├── lib                   Generated libraries
│   ├── MyLibgcc.a        Release library
│   └── MyLibgccD.a       Debug library
├── doc                   Documentation
│   ├── Requirements.mmd  Detailed requirements
│   └── dox               Doxygen-generated design documentation
│       └── index.html
├── SwDev                 Make scripts
│   ├── project.mk        CPN, version, and project-specific settings
│   ├── gcc.mk            Generic compiler-specific make script
│   └── build.mk          Generic make script
├── src                   Source code
│   ├── Makefile          Compiles the source code in this directory
│   ├── InternalIncludes.h These header files are not visible to the user.
│   ├── MyLib.cpp
│   ├── Pimpl.cpp
│   └── Class.cpp
└── tests                 Unit tests
   └── Makefile           Builds and runs the unit tests

MyLib

This is the top-level directory for the library.

README.mmd

README.mmd is the first place a new-user will look for information. It should provide a brief description of the library, build instructions, and how to find additional information.

The .mmd extension signifies that this file is in MultiMarkdown format, the preferred format for software documentation.

MyLib

This directory contains the user-interface header files for the library. It has the same name as the library, providing users with a namespaced include.

#include "MyLib/Class.h"

These header files provide complete doxygen documentation. Here are some ?guidelines for using doxygen.

The header files in this directory may not #include header files from the src directory nor other tool directories that are only used when generating the library itself.

MyLib.h

MyLib.h represents one or more header files that provide library-specific types, constants, variable, free functions, and light-weight classes.

The example shows how library singletons are handled, even though the use of singletons is discouraged. The name of the singleton always begins with "The". A std::unique_ptr is used to manage the lifetime of the singleton, so that it cannot be used before it is initialized. A library that has any singletons provides an Initialize() function that constructs the singletons. A library that does not have any singletons does not need to provide Initialize().

Pimpl.h

Pimpl.h provides a template for the pointer-to-implementation C++ idiom. This is an effective way to hide implementation detail from the user and the compiler for heavy-weight classes, resulting in faster compile and link times. However, the "pimpl" idiom is overkill for middle-weight classes and does not work well with inheritance. Pimpl classes cannot contain template member functions either.

A std::unique_ptr is used to hold the class' implementation which is hidden in src/Pimpl.cpp.

Usually, pimpl classes are not copyable, so their copy-constructor and copy-assignment are deleted.

private:
  Pimpl(const Pimpl&) = delete;
  Pimpl& operator=(const Pimpl&) = delete;

Pimpl's are typically movable, and the move-semantics is provided inline.

Class.h

Class.h provides a template for middle-weight classes. The implementation of middle-weight classes does not depends only on other libraries, without any internal implementation details. Short and templated member functions can be efficiently inlined in this header file. Implementation-specific member variables use a managed pointer, typically std::unique_ptr or std::shared_ptr, to hide their implementation in the C++ source file, similar to the pimpl idiom.

doc

The doc directory contains user-oriented detailed requirements and design documentation. Other detailed design documentation resides in the src directory along with the source code or in a separate detailed design document.

SwDev

SwDev is only present in libraries that have their own Collins Part Number. It contains make scripts that are used to identify the project settings, compiler-specific make scripts, and the make rules used throughout the project. It typically contains the following files.

| File | Description |
| :------------- | :------------------------------------------ |
| project.mk | CPN, version, and project-specific settings |
| gcc.mk | Generic compiler-specific make script |
| build.mk | Generic make script |
[SwDev Make Scripts]

A typical project.mk defines at least the following make variables.

| Variable | Description |
| :--------- | :----------------------------------------- |
| CPN | Collins Part Number |
| VERSION | Version number in Major.Minor.Build format |
| DEBUG | Debug level, undefined mean "release" |
| COMPILER | Compiler/target/tool-chain |
| PLATFORM | Target platform/operating system. |
| PATH_OBJ | Path for temporary files. |
| DBGSFX | "D" if DEBUG is defined, "" otherwise. |
| CLEAN | Filename glob to use for make clean. |
[project.mk Make Variables]

src

The src directory contains the source code that implements a library and the Makefile the builds it. Header files that are not part of the user interface also belong here.

Makefile

The Makefile contains instructions for building a library. Libraries use a common Makefile format. Let's consider the example makefile a little at a time.

PROJDIR := $(abspath ..)

PROJDIR points to the top of this project's directory tree. It's a relative path, so that the project can be built from anywhere. Locations for files outside of the currently library are all referenced relative to $(PROJDIR).

SWDEV ?= $(PROJDIR)/SwDev

If SWDEV is not already defined, it defaults to the current projects [SwDev][] directory. Super-projects define SWDEV in their higher-level Makefiles so that the subprojects all use the same compiler settings and build rules.

include $(PROJDIR)/SwDev/project.mk

The above line refers to the project-specific makefile [project.mk[].

TARGET1 = $(PROJDIR)/lib/MyLib$(COMPILER)$(DBGSFX).$A
TARGETS = $(TARGET1)

The above lines define the libraries that will be built. Notice the use of "=" instead of ":=". make evaluates the right-hand-side of ":=" before the assignment. make does not immediately evaluate the right-hand-side of '=', however. This is important for TARGET1 and TARGETS above, because the value for $A is not known until gcc.mk is included below.

SOURCE := MyLib.cpp Class.cpp Pimpl.cpp

SOURCE is a list of source files to be compiled, not including header files.

SYSINCL := $(RCI) $(BOOST)

SYSINCL is a list of directories to be searched for header files. These files are not included in the library's dependency list.

INCLUDE := ..
INCLUDE += $(PROJDIR)/OtherLib

INCLUDE is another list of directories to be searched for header files. These files are included in the library's dependency list.

include $(SWDEV)/$(COMPILER).mk
include $(SWDEV)/build.mk

The above two lines include the project-specific make scripts.

$(TARGET1): $(OBJ)

Finally, the last causes the compiled object modules to be installed into the library archive. The action is inferred from the target filename extension.

InternalIncludes.h

Header files that are not part of the library's user-interface belong in the src directory.

MyLib.cpp

MyLib.cpp provides the implementation detail for [Mylib.h][].

Pimpl.cpp

Pimpl.cpp provides the implementation detail for [Pimpl.h][]. It defines the Pimpl::Impl class and defines the public Pimpl member functions in terms of Pimpl::Impl member functions, usually with the same name and signature.

Class.cpp

Class.cpp provides the implementation detail for [Class.h][]. This file only defines methods that are not already defined inline in the class declaration.

tests

The tests directory contains the unit tests for the library. Running make in that directory builds and runs the unit tests.