We use PEP 8 with one exception, we extend the 80 char line limit to 120.
Header files use the extension ".h". Definitions of functions that must be in the header should be moved to the end of the header file in a region surrounded by #pragma region implementation
/#pragma endregion implementation
blocks. Source code files use the extension ".cpp" if they are compiled into a .lib or .exe. Each file should typically contain a single class and its name should match the class name.
A project is a set of source files that compile into a single executable or library, or that implement a single template library.
Projects have camelCase names, preferably single word, such as "model", "utilities", "compile".
All of the source code in a project should be defined in a namespace that shares the project name. All of the source files associated with a project should be contained in a directory that shares the project name. The executable or library generated by a project should share the project name.
Each project is contained in a directory. The directory contains a CMakeLists.txt file. The directory typically contains the following subdirectories:
- "include", for h files
- "src" for cpp files (unless the project defines a template library without cpp files)
- "test" for source files that define unit tests, invoked via ctest
- "doc" for documentation that does not fit in the source files themselves. For example, complex math algorithms may require detailed documentation in a LaTeX file, which would live in the doc directory
Additionally, the directory can contain a README.md file.
Names should be descriptive and self-evident, rather than terse. Use whole words rather than abbreviations. Example: use
GetFunctionDefinition
instead of GetFuncDef
. Exceptions to this rule are:
- Use "Num" as abbreviation for "Number"
- Use
Size
for a member function that returns the size of an object (instead ofGetSize
)
Classes, structs, enum classes:
use PascalCase, e.g., class MyClass {};
use camelCase for name of enum values e.g., enum MyEnum { valueOne, valueTwo };
Functions, member and non-member:
use PascalCase, e.g., int GetValue() const { return _value; }
When possible, function names should be imperative mood verbs, e.g., GetValue()
and Permute()
. Specifically, accessor member functions
should start with Get
, e.g., GetOutputVector()
.
Exceptions to this rule are:
- member functions that returns the size of an object, which can simply be
Size()
- type conversion functions, which can start with
To
, e.g.,ToString()
- functions that return the number of elements in a collection, which can start with
Num
, e.g.NumVariables()
Method and Function arguments: camelCase: e.g., int GetValue(int rowIndex, int columnIndex);
Member variables:
Use _
(underscore) followed by camelCase, e.g., _myMemberVariable
Template typenames:
Use PascalCase and add the suffix "Type", e.g., template <typename SparseVectorType>
Template variables that are not typenames: Use camelCase, e.g., template <size_t size>
All source code files should start with a header that specifies project name, file name, and author list (see example below)
.h files should indicate #pragma once
immediately after the header
Next, indicate all #include
statements. First, include files from the local project. Second, include files from other projects, grouping
by project, ideally in an alphabetical order. Next include third-party library headers, like LLVM. Finally, include standard libraries.
////////////////////////////////////////////////////////////////////////////////////////////////////
//
// Project: Embedded Learning Library (ELL)
// File: FileName.h (libraryName)
// Authors: Author1, Author2
//
////////////////////////////////////////////////////////////////////////////////////////////////////
#pragma once
#include "OtherClassFromThisProject.h"
#include <proj1/include/ClassFromProjectProj1.h>
#include <proj1/include/AnotherClassFromProj1.h>
#include <proj2/include/ClassFromProj2.h>
#include <vector>
#include <string>
In certain cases, normal #ifndef
-style include guards are required instead of using #pragma once
. In this case, add the guard name
in a comment after the #endif
:
#ifndef MATRIX_H
#define MATRIX_H
...
#endif // MATRIX_H
Almost all function implementations belong in .cpp and in the implementation region of .h files. The exception is short single-instruction implementations of parameterless functions, which should appear in the .h file on the same line as the function declaration. For example:
double GetValue() const { return _value; } // inline implementation in .h file
double SetValue(double value); // function has parameters - implementation belongs in .cpp or the implementation region of the .h file
void Next() { _iterator++; _count++; } // wrong: multi-instruction implementations belong in .cpp or the implementation region of the .h files
virtual
functions declared in derived classes should use the override
or final
keywords, not virtual
. This is because override
and
final
imply virtual
. Removing virtual
also protects against unknowingly adding another virtual member function instead of overriding a
member function from a base class, especially in the case where a virtual function has been removed from the base class.
Variables that are declared or defined but never read from will emit an unused variable warning on all compilers. Avoid unused variables as
much as possible. If you cannot remove the unused variable, then mark all such variables with the UNUSED
macro. If a variable is only used
in code that conditionally compiles for debug builds (such as assert
expressions), it should be marked as such with the DEBUG_USED
macro, instead of the UNUSED
macro. Both UNUSED
and DEBUG_USED
are defined in Unused.h
in the utilities
library.
Use clang-format to enforce correct formatting. Use the definitions in the file .clang-format
, which is found in the main project directory.
All public classes, functions, and variables that are declared in .h files should be preceded by an XML documentation block. The only exception is functions that are defined with =default or =delete, which should not be documented. Each documentation row should begin with three slashes (///) and the first line in a documentation block should contain a summary tag. Follow this example:
/// <summary> Mathematical power operation. </summary>
///
/// <param name="base"> The base number. </param>
/// <param name="exponent"> The exponent number. </param>
///
/// <returns> Base number to the power of the exponent number. </returns>
double Power(double base, double exponent);
/// <summary> Performs an in-place sort. </summary>
///
/// <typeparam name="RandomAccessContainerType"> The container type, which must implement a square bracket operator. </typeparam>
/// <param name="container"> [in,out] The container being sorted. </param>
template <typename RandomAccessContainerType>
void InplaceSort(RandomAccessContainerType& container);
The c "assert" function usually results in immediate termination of the program, so this should only be used in cases where it should never happen unless there is a logic error in our code. To this end assert documents the existing invariants, preconditions and post conditions in the code.
Bad input parameters from our public API or bad data read from a file should not result in this kind of assert termination because it is hard to debug, and it provides no useful information to the caller. Instead, throw the appropriate type of exception as defined in ~/libraries/utilities/include/Exception.h. This includes the notImplemented case, which you can raise using throw utilities::LogicException(utilities::LogicExceptionErrors::notImplemented);