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.