Gluten CPP Core Guidelines
This is a set of CPP core guidelines for Gluten. The aim is to make the codebase simpler, more efficient, more maintainable by promoting consistency and according to best practices.
Philosophy
Philosophical rules are generally not measurable. However, they are valuable. For Gluten CPP coding, there are a few Philosophical rules as the following.
- Write in ISO Standard C++.
- Standard API first, the CPP programming APIs are priority to system calls.
- Write code consistently. It’s good for understanding and maintaining.
- Keep simple, making code clear and easy to read.
- Optimize code for reader, not for writer. Thus, more time will be spent reading code than writing it.
- Make it work, and then make it better or faster.
- Don’t import any complexity if possible. Collaborate with minimal knowledge consensus.
Code Formatting
Many aspects of C++ coding style will be covered by clang-format, such as spacing, line width, indentation and ordering (for includes, using directives and etc).
- Always ensure your code is compatible with clang-format-12 for Velox backend.
dev/formatcppcode.sh
is provided for formatting Velox CPP code.
Naming Conventions
- Use PascalCase for types (class, struct, enum, type alias, type template parameter) and file name.
- Use camelCase for function, member and local variable, and non-type template parameter.
- Use camelCase_ for private and protected member variable.
- Use snake_case for namespace name and build target.
- Use UPPER_SNAKE_CASE for macro.
- Use kPascalCase for static constant and enumerator.
Designs
- No over design.
- No negation of negation,
isValid
is better thanisNotInvalid
. - Avoid corner case, and common case first.
- Express ideas directly, don’t let me think.
- Make a check for the arguments in the interface between modules, and don’t make a check in the inner implementation, use
assert
in the private implementation instead of too much safe check.
Source File & Header File
- All header files must have a single-inclusion guard using
#pragma once
- Always use
.h
as header file suffix, not.hpp
. - Always use
.cc
as source file suffix, neither.cpp
nor.cxx
. - One file should contain one main class, and the file name should be consistent with the main class name.
- Obvious exception: files used for defining various misc functions.
- If a header file has a corresponding source file, they should have the same file name with different suffix, such as
a.h vs a.cc
. - If a function is declared in the file
a.h
, ensure it’s defined in the corrosponding source filea.cc
, do not define it in other files. - No deep source directory for CPP files, not do it as JAVA.
- Include header files should satisfy the following rules.
- Include the necessary header files, which means the source file (.cc) containing the only one line
#include "test.h"
can be compiled successfully without including any other header files. - Do not include any unnecessary header files, the more including, the slower compiling.
- In one word, no more, no less, just as needed.
- Include the necessary header files, which means the source file (.cc) containing the only one line
Class
- Base class name doesn’t end with Base, use
Backend
instead ofBackendBase
. - Ensure one class does one thing, and follows the single responsibility principle.
- No big class, No huge class, No too much interfaces.
- Distinguish interface from implementation, make implementations private.
- When designing a class hierarchy, distinguish between interface inheritance and implementation inheritance.
- Ensure that public inheritance represent the relation of
is-a
. - Ensure that private inheritance represent the relation of
implements-with
.
- Ensure that public inheritance represent the relation of
- Don’t make a function
virtual
without reason. - Ensure the polymorphic base class has a
virtual
deconstructor. - Use
override
to make overriding explicit and to make the compiler work. - Use
const
to mark the member function read-only as far as possible. - When you try to define a
copy constructor
or aoperator=
for a class, remember theRule of three/five/zero
.
Function
- Make functions short and simple.
- Calling a meaningful function is more readable than writing too many statements in place, but the performance-sensitive code path is an exception.
- Give the function a good name, how to check whether the function name is good or not.
- When you read it loudly, you feel smooth.
- The information can be represented by arguments should not be encoded into the function name. such as. use
get(size_t index)
instead ofgetByIndex
.
- A function should focus on a single logic operation.
- A function should do as the name meaning.
- do everything converd by the function name
- don’t do anything not convered by the function name
Variable
- Make variable names simple and meaningful.
- Don’t group all your variables at the top of the scope, it’s an outdated habit.
- Declare variables as close to the usage point as possible.
Constant
- Prefer const variables to using preprocessor (
#define
) to define constant values.
Macro
- Macros downgrade readability, break mind, and affect debug.
- Macros have side effects.
- Use macros cautiously and carefully.
- Consider using
const
variables orinline
functions to replace macros. - Consider defining macros with the wrap of
do {...} while (0)
- Avoid using 3rd party library macros directly.
Namespace
- Don’t
using namespace xxx
in header files. Instead, you can do this in source files. But it’s still not encouraged. - Place all Gluten CPP codes under
namespace gluten
because one level namespace is enough. No nested namespace. Nested namespaces bring mess. - The anonymous namespace is recommended for defining file level classes, functions and variables. It’s used to place file scoped static functions and variables.
Resource Management
- Use handles and RAII to manage resources automatically.
- Immediately give the result of an explicit resource allocation to a manager object.
- Prefer scoped objects and stack objects.
- Use raw pointers to denote individual objects.
- Use
pointer + size_t
to denote array objects if you don’t want to use containers. - A raw pointer (a
T*
) is non-owning. - A raw reference (a
T&
) is non-owning. - Understand the difference of
unique_ptr
,shared_ptr
,weak_ptr
.unique_ptr
represents ownership, but not share ownership.unique_ptr
is equivalent to RAII, release the resource when the object is destructed.shared_ptr
represents shared ownership by use-count. It is more expensive thatunqiue_ptr
.weak_ptr
models temporary ownership. It is useful in breaking reference cycles formed by objects managed byshared_ptr
.
- Use
unique_ptr
orshared_ptr
to represent ownership. - Prefer
unique_ptr
overshared_ptr
unless you need to share ownership. - Use
make_unique
to makeunique_ptr
s. - Use
make_shared
to makeshared_ptr
s. - Take smart pointers as parameters only to explicitly express lifetime semantics.
- For general use, take
T*
orT&
arguments rather than smart pointers.
Exception
- The exception specifications are changing always. The difference between various CPP standards is big, so we should use exception cautiously in Gluten.
- Prefer
return code
to throwing exceptions. - Prefer compile-time checking to run-time checking.
- Encapsulate messy constructors, rather than spreading through the code.
Code Comment
- Add necessary comments. The comment is not the more the better, also not the less the better.
- Good comment makes obscure code easily understood. It’s unnecessary to add comments for quite obvious code.
References
- CppCoreGuidelines
- Velox CODING_STYLE
- Thanks Gluten developers for their wise suggestions and helps.