This document specifies the coding style for the C++ Actor Framework. The style is loosely based on the Google C++ Style Guide and the coding conventions used by the STL.
// libcaf_example/caf/example/my_class.hpp
#ifndef CAF_EXAMPLE_MY_CLASS_HPP
#define CAF_EXAMPLE_MY_CLASS_HPP
#include <string>
namespace caf {
namespace example {
/**
* This class is only being used as style guide example.
*/
class my_class {
public:
/**
* Brief description. More description. Note that CAF uses the
* "JavaDoc-style" autobrief option, i.e., everything up until the
* first dot is the brief description.
*/
my_class();
/**
* Destructs `my_class`. Please use Markdown in comments.
*/
~my_class();
// do not use the @return if you start the brief description with "Returns"
/**
* Returns the name of this instance.
*/
inline const std::string& name() const {
return m_name;
}
/**
* Sets the name of this instance.
*/
inline void name(std::string new_name) {
m_name = std::move(new_name);
}
/**
* Prints the name to `STDIN`.
*/
void print_name() const;
/**
* Does something (maybe).
*/
void do_something();
/**
* Does something else.
*/
void do_something_else();
private:
std::string m_name;
};
} // namespace example
} // namespace caf
#endif // CAF_EXAMPLE_MY_CLASS_HPP// libcaf_example/src/my_class.cpp
#include "caf/example/my_class.hpp"
#include <iostream>
namespace caf {
namespace example {
namespace {
constexpr const char default_name[] = "my object";
} // namespace <anonymous>
my_class::my_class() : m_name(default_name) {
// nop
}
my_class::~my_class() {
// nop
}
void my_class::print_name() const {
std::cout << name() << std::endl;
}
void my_class::do_something() {
if (name() == default_name) {
std::cout << "You didn't gave me a proper name, so I "
<< "refuse to do something."
<< std::endl;
} else {
std::cout << "You gave me the name " << name()
<< "... Do you really think I'm willing to do something "
"for you after insulting me like that?"
<< std::endl;
}
}
void::my_class::do_something_else() {
switch (default_name[0]) {
case 'a':
// handle a
break;
case 'b':
// handle b
break;
default:
handle_default();
}
}
} // namespace example
} // namespace caf
-
Use 2 spaces per indentation level.
-
The maximum number of characters per line is 80.
-
Never use tabs.
-
Vertical whitespaces separate functions and are not used inside functions: use comments to document logical blocks.
-
Header filenames end in
.hpp, implementation files end in.cpp. -
Never declare more than one variable per line.
-
*and&bind to the type, e.g.,const std::string& arg. -
Namespaces do not increase the indentation level.
-
Access modifiers, e.g.
public, are indented one space. -
Use the order
public,protected, and thenprivate. -
Always use
autoto declare a variable unless you cannot initialize it immediately or if you actually want a type conversion. In the latter case, provide a comment why this conversion is necessary. -
Never use unwrapped, manual resource management such as
newanddelete. -
Do not use
typedef. The syntax provided byusingis much cleaner. -
Use
typenameonly when referring to dependent names. -
Keywords are always followed by a whitespace:
if (...),template <...>,while (...), etc. -
Always use
{}for bodies of control structures such asiforwhile, even for bodies consiting only of a single statement. -
Opening braces belong to the same line:
if (my_condition) { my_fun(); } else { my_other_fun(); }
-
Use standard order for readability: C standard libraries, C++ standard libraries, other libraries, (your) CAF headers:
#include <sys/types.h> #include <vector> #include "some/other/library.hpp" #include "caf/example/myclass.hpp"
-
When declaring a function, the order of parameters is: outputs, then inputs. This follows the parameter order from the STL.
-
Never use C-style casts.
-
Class names, constants, and function names are all-lowercase with underscores.
-
Types and variables should be nouns, while functions performing an action should be "command" verbs. Classes used to implement metaprogramming functions also should use verbs, e.g.,
remove_const. -
Member variables use the prefix
m_. -
Thread-local variables use the prefix
t_. -
Static, non-const variables are declared in the anonymous namespace and use the prefix
s_. -
Template parameter names use CamelCase.
-
Getter and setter use the name of the member without the
m_prefix:class some_fun { public: // ... int value() const { return m_value; } void value(int new_value) { m_value = new_value; } private: int m_value; };
-
Use
Tfor generic, unconstrained template parameters andxfor generic function arguments. Suffix both withsfor template parameter packs:template <class... Ts> void print(const Ts&... xs) { // ... }
-
Each
.cppfile has an associated.hppfile. Exceptions to this rule are unit tests andmain.cppfiles. -
Each class has its own pair of header and implementation files and the relative path for the header file is derived from its full name. For example, the header file for
caf::example::my_classoflibcaf_exampleis located atlibcaf_example/caf/example/my_class.hppand the source file atlibcaf_example/src/my_class.cpp. -
All header files should use
#defineguards to prevent multiple inclusion. The symbol name is<RELATIVE>_<PATH>_<TO>_<FILE>_HPP. -
Do not
#includewhen a forward declaration suffices. -
Each library component must provide a
fwd.hppheader providing forward declartations for all types used in the user API. -
Each library component must provide an
all.hppheader that contains the main page for the documentation and includes all headers for the user API. -
Use
inlinefor small functions (rule of thumb: 10 lines or less).
-
Break constructor initializers after the comma, use four spaces for indentation, and place each initializer on its own line (unless you don't need to break at all):
my_class::my_class() : my_base_class(some_function()), m_greeting("Hello there! This is my_class!"), m_some_bool_flag(false) { // ok } other_class::other_class() : m_name("tommy"), m_buddy("michael") { // ok }
-
Break function arguments after the comma for both declaration and invocation:
intptr_t channel::compare(const abstract_channel* lhs, const abstract_channel* rhs) { // ... }
-
Break before tenary operators and before binary operators:
if (today_is_a_sunny_day() && it_is_not_too_hot_to_go_swimming()) { // ... }
Despite its power, template metaprogramming came to the language pretty much by accident. Templates were never meant to be used for compile-time algorithms and type transformations. This is why C++ punishes metaprogramming with an insane amount of syntax noise. In CAF, we make excessive use of templates. To keep the code readable despite all the syntax noise, we have some extra rules for formatting metaprogramming code.
-
Brake
using name = ...statements always directly after=if it does not fit in one line. -
Consider the semantics of a metaprogramming function. For example,
std::conditionalis an if-then-else construct. Hence, place the if-clause on its own line and do the same for the two cases. -
Use one level of indentation per "open" template and place the closing
>,>::typeor>::valueon its own line. For example:using optional_result_type = typename std::conditional< std::is_same<result_type, void>::value, bool, optional<result_type> >::type; // think of it as the following (not valid C++): auto optional_result_type = conditional { if result_type == void then bool else optional<result_type> };
-
Note that this is not necessary when simply defining a type alias. When dealing with "ordinary" templates, indenting based on the position of the opening
<is ok, e.g.:using response_handle_type = response_handle<Subtype, message, ResponseHandleTag>;
-
Use rvalue references whenever it makes sense.
-
Write move constructors and assignment operators if possible.
-
Use macros if and only if you can't get the same result by using inline functions or proper constants.
-
Macro names use the form
CAF_<COMPONENT>_<NAME>.
-
The first line is a capitalized summary and has at most 50 characters.
-
The second line is left empty.
-
Optional long descriptions begin on the third line with a at most 72 characters per line.
-
Write commit messages in the imperative: "Fix bug" and not "Fixes bug" or "Fixed bug".
-
The
masterbranch always reflects a production-ready state, i.e., the latest release version. -
The
developbranch is the main branch. Every time it reaches a stable point it is merged back tomasterfor a new release. Thedevelopbranch reflects the latest state of development and should always compile. -
Simple bugfixes consisting of a single commit can be pushed to
develop. -
Fixes for critical issues can be pushed to the
master(anddevelop) branch after talking to the maintainer (and then cause a new release immediately). -
New features or non-trivial fixes are developed in a topic branch using the naming convention
topic/short-description. Topic branches are merged off fromdevelop. Finishing topic branches includes these steps:- Create a pull request to
developon GitHub - Wait for the results of Jenkins and fix any issues that might come up
- Squash your commits if necessary once Jenkins succeeds
- Ask the maintainer to review your changes if you have green light from Jenkins and a clean Git history for your work
- The maintainer will merge the topic branch back into
developafter the review phase is done (and you once again have a clean Git history)
- Create a pull request to