Difference between revisions of "C++ Style Guide"
| Line 5: | Line 5: | ||
Finally, this document is constantly evolving and as such older projects may not be up-to-date with the guidelines found here. In those circumstances one must use their best judgment about how to integrate these guidelines into older codebases. As a general principle, consistency should favor the local over the global, that is it is more important to be consistent with a function definition than within a file, and more important to be consistent within a file than within a directory, project, etc... | Finally, this document is constantly evolving and as such older projects may not be up-to-date with the guidelines found here. In those circumstances one must use their best judgment about how to integrate these guidelines into older codebases. As a general principle, consistency should favor the local over the global, that is it is more important to be consistent with a function definition than within a file, and more important to be consistent within a file than within a directory, project, etc... | ||
| − | =Core Guidelines= | + | = Relationship to the Cpp Core Guidelines = |
| + | |||
This document should be seen as an extension to the Cpp Core Guidelines authored and maintained by Herb Sutter and Bjarne Stroustrup. After reviewing these guidelines one should then become familiar with the Cpp Core Guidelines as documented here: | This document should be seen as an extension to the Cpp Core Guidelines authored and maintained by Herb Sutter and Bjarne Stroustrup. After reviewing these guidelines one should then become familiar with the Cpp Core Guidelines as documented here: | ||
| − | https://github.com/isocpp/CppCoreGuidelines | + | * [https://github.com/isocpp/CppCoreGuidelines Cpp Core Guidelines] |
Those guidelines specify the best practices for writing modern C++ code. In situations where there is a conflict between the Cpp Core Guidelines and the guidelines set forth in this document, this document takes precedence. For all known situations where a conflict exists, this document should explicitly indicate that conflict to avoid confusion. | Those guidelines specify the best practices for writing modern C++ code. In situations where there is a conflict between the Cpp Core Guidelines and the guidelines set forth in this document, this document takes precedence. For all known situations where a conflict exists, this document should explicitly indicate that conflict to avoid confusion. | ||
| − | =Development Environment= | + | = Development Environment = |
| − | The two primary development environments supported by Spire projects are | + | All projects target the C++23 standard. The two primary development environments supported by Spire projects are Linux and Windows. CMake is used to produce the appropriate project/make files for each platform. |
For source control, git is used and projects are to be hosted on [https://github.com/spiretrading Spire Trading's Github page]. | For source control, git is used and projects are to be hosted on [https://github.com/spiretrading Spire Trading's Github page]. | ||
| − | Each developer is welcome to use an editor of their choice. | + | Each developer is welcome to use an editor of their choice. |
| − | |||
| − | |||
| − | + | = File and Directory Naming = | |
| − | + | Use [https://en.wikipedia.org/wiki/CamelCase CamelCase] for header and source file names, matching the primary type or class defined within. The main exception is for CMake files which must use the name ''CMakeLists.txt''. Directory names use [https://en.wikipedia.org/wiki/Snake_case snake case] with all lower case letters. | |
| − | + | = Project Structure = | |
| − | + | A project is broken down into one library component and one or more application components. The library is written in a [https://en.wikipedia.org/wiki/Header-only header-only fashion], that is the implementation is provided directly in the header file rather than a separate .cpp source file. This allows for such libraries to easily be incorporated into other projects without the need for complex build configurations. Applications often use a mix of header only files and source files. | |
| − | + | At the root of a project are the build scripts: <code>build.bat</code>/<code>build.sh</code> to build the project, <code>configure.bat</code>/<code>configure.sh</code> to produce the platform specific build files (VS solutions for Windows, make files for POSIX), and <code>setup.bat</code>/<code>setup.sh</code> to download all third party dependencies. Some projects may also include ''install'' scripts, ''version'' scripts, and scripts for continuous builds or deployment. | |
| − | + | The library is organized into the following directories: | |
| − | + | * ''Include'' - Header files (.hpp), organized by component under a project-level namespace directory. | |
| + | * ''Source'' - Unit test source files (.cpp), organized into ''ComponentTests'' directories. | ||
| + | * ''Config'' - CMake build configuration files, with a subdirectory per component. | ||
| + | * ''Dependencies'' - Third party dependencies downloaded by the setup script. | ||
| − | + | Applications go into a separate ''Applications'' directory at the project root. Each application follows the same structure with its own ''Include'', ''Source'', ''Config'', and ''Dependencies'' directories alongside its own build scripts. | |
| − | + | The following example shows the structure of a project named ''TicTacToe'' consisting of a library with two components (Board and Rules), and two applications (TicTacToeConsole and TicTacToeUi): | |
<pre> | <pre> | ||
| − | + | TicTacToe # Root level directory. | |
| − | \ | + | build.bat # Builds all libraries and applications. |
| − | \ | + | configure.bat # Produces platform specific build files. |
| − | + | setup.bat # Downloads all third party dependencies. | |
| − | + | \Applications # Contains all applications. | |
| − | + | \TicTacToeConsole # A console application. | |
| − | + | build.bat # Application build scripts. | |
| − | + | CMakeLists.txt # Top-level CMake config for the application. | |
| − | + | \Config # CMake build files per component. | |
| − | + | \TicTacToeConsole # CMake config for the main executable. | |
| − | + | \Include # Application header files. | |
| − | + | \TicTacToeConsole # Namespace directory. | |
| − | + | \Source # Application source files. | |
| − | \ | + | \TicTacToeConsole # Source files for the application. |
| − | + | \TicTacToeUi # A GUI application. | |
| − | + | \... # Same structure as TicTacToeConsole. | |
| − | + | \TicTacToe # The main library. | |
| − | + | build.bat # Library build scripts. | |
| − | \ | + | setup.bat # Downloads library dependencies. |
| − | \ | + | CMakeLists.txt # Top-level CMake config for the library. |
| − | + | \Config # CMake build files per component. | |
| − | + | \Board # CMake config for the Board component. | |
| − | + | \Rules # CMake config for the Rules component. | |
| − | + | \Include # Library header files. | |
| − | + | \TicTacToe # Namespace directory. | |
| − | + | \Board # Headers for the Board component. | |
| − | + | \BoardTests # Test utility headers. | |
| − | + | \Rules # Headers for the Rules component. | |
| − | + | \Source # Library source and test files. | |
| − | \ | + | \BoardTests # Unit tests for the Board component. |
| − | \... # | + | \RulesTests # Unit tests for the Rules component. |
| − | \ | + | \Dependencies # Third party dependencies. |
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | \ | ||
| − | \ | ||
| − | |||
| − | |||
| − | \ | ||
| − | |||
| − | |||
| − | |||
| − | |||
| − | \ | ||
| − | |||
| − | |||
| − | |||
| − | |||
| − | \ | ||
| − | \ | ||
| − | |||
| − | |||
| − | |||
| − | \ | ||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
</pre> | </pre> | ||
| − | + | = Header File Structure = | |
| + | |||
| + | == Include Guards == | ||
| − | + | Header files are structured first with an [https://en.wikipedia.org/wiki/Include_guard include guard] defined using SNAKE_CASE whose name consists of the project name appended to the directory name and finally the file name itself, ending with a ''_HPP'' suffix. As a note, all files end with a single new line character. | |
| − | |||
| − | |||
<syntaxhighlight lang=c++ line=line> | <syntaxhighlight lang=c++ line=line> | ||
#ifndef CPP_CHAT_SERVER_HPP | #ifndef CPP_CHAT_SERVER_HPP | ||
| Line 144: | Line 90: | ||
</syntaxhighlight> | </syntaxhighlight> | ||
| − | ==Include Directives== | + | == Include Directives == |
| − | Next come the list of #include directives. Include files are ordered based on their category where the first category is standard C++ header files, the second category is external dependency header files, and finally the third category is project header files. Both standard and external dependency header files use angle brackets (#include <...>) and local project header files use quotes (#include "..."). Within each category files are listed in alphabetical order. | + | |
| + | Next come the list of #include directives. Include files are ordered based on their category where the first category is standard C++ header files, the second category is external dependency header files, and finally the third category is project header files. Both standard and external dependency header files use angle brackets (<code>#include <...></code>) and local project header files use quotes (<code>#include "..."</code>). Within each category files are listed in alphabetical order. | ||
| + | |||
<syntaxhighlight lang=c++ line=line> | <syntaxhighlight lang=c++ line=line> | ||
#include <tuple> | #include <tuple> | ||
| Line 154: | Line 102: | ||
</syntaxhighlight> | </syntaxhighlight> | ||
| − | ==Namespaces== | + | == Namespaces == |
| − | After include directives the project's namespace is defined. All declarations and definitions must be contained within a namespace so as to avoid polluting the global namespace. Namespace definitions should be preceded by one single new line except when immediately following a namespace definition. The top level namespace is named after the project, and sub-namespaces may be used (although they should be used very sparingly). | + | |
| + | After include directives the project's namespace is defined. All declarations and definitions must be contained within a namespace so as to avoid polluting the global namespace. Namespace definitions should be preceded by one single new line except when immediately following a namespace definition. The top level namespace is named after the project, and sub-namespaces may be used (although they should be used very sparingly). When possible, use the nested namespace syntax (''A::B'') instead of separate nested declarations. | ||
<syntaxhighlight lang=c++ line=line> | <syntaxhighlight lang=c++ line=line> | ||
| Line 163: | Line 112: | ||
} | } | ||
| − | namespace SubChatB | + | namespace SubChatB::SubSubChatA { |
| − | |||
... | ... | ||
| − | |||
} | } | ||
} | } | ||
| Line 177: | Line 124: | ||
One situation where nested namespaces are welcome and encouraged are for implementation details that do not form part of the namespace's public interface. These definitions are put in a nested namespace called ''Details'' and go into a file of their own whose name contains the suffix ''details''. For example if the ''cpp_chat_server.hpp'' contains implementation details, then they should be included in the file ''cpp_chat_server_details.hpp'' and the definitions should go into the namespace ''CppChat::Details''. | One situation where nested namespaces are welcome and encouraged are for implementation details that do not form part of the namespace's public interface. These definitions are put in a nested namespace called ''Details'' and go into a file of their own whose name contains the suffix ''details''. For example if the ''cpp_chat_server.hpp'' contains implementation details, then they should be included in the file ''cpp_chat_server_details.hpp'' and the definitions should go into the namespace ''CppChat::Details''. | ||
| − | =Layout= | + | = Layout = |
| + | |||
| + | == Indentation == | ||
| − | |||
Code is indented using 2 spaces per level, tabs are not permitted. | Code is indented using 2 spaces per level, tabs are not permitted. | ||
| Line 191: | Line 139: | ||
</syntaxhighlight> | </syntaxhighlight> | ||
| − | ==Line Structure== | + | == Line Structure == |
| − | Lines are limited to 80 characters. Exceptions to this rule are long include files and long string literals where breaking up the literal into multiple lines is not possible. In order to break up long statements into multiple lines the | + | |
| + | Lines are limited to 80 characters. Exceptions to this rule are long include files and long string literals where breaking up the literal into multiple lines is not possible. In order to break up long statements into multiple lines, break the line after the earliest operator or punctuation mark (which includes commas, operators, and opening brackets). | ||
| − | |||
<syntaxhighlight lang=c++ line=line> | <syntaxhighlight lang=c++ line=line> | ||
| − | |||
// Correct, line break at comma. | // Correct, line break at comma. | ||
f(a, ..., b, | f(a, ..., b, | ||
| Line 222: | Line 169: | ||
</syntaxhighlight> | </syntaxhighlight> | ||
| − | + | For statements that begin new blocks of code (such as if/while/class...), in order to avoid confusing the line continuation with the code block, the line continuation is indented two extra levels. For example consider the following snippet of code: | |
<syntaxhighlight lang=c++ line=line> | <syntaxhighlight lang=c++ line=line> | ||
| Line 244: | Line 191: | ||
The extra level of indentation in the line continuation makes it clear where the condition ends and the code block begins. | The extra level of indentation in the line continuation makes it clear where the condition ends and the code block begins. | ||
| − | ==Braces== | + | == Blank Lines == |
| + | |||
| + | Documented declarations shall be separated from the following declaration by a blank line. Any two non-documented declarations shall not have a blank line between them. In general, public declarations shall be documented. This rule applies to all declarations including class members, free functions, and free variables. | ||
| + | |||
| + | <syntaxhighlight lang=c++ line=line> | ||
| + | class Connection { | ||
| + | public: | ||
| + | |||
| + | /** Constructs a Connection from a host and port. */ | ||
| + | Connection(std::string host, int port); | ||
| + | |||
| + | Connection(Connection&& other) noexcept; | ||
| + | ~Connection(); | ||
| + | |||
| + | /** Sends a message over the connection. */ | ||
| + | void send(const std::string& message); | ||
| + | |||
| + | /** Returns true if the connection is open. */ | ||
| + | bool is_open() const; | ||
| + | }; | ||
| + | </syntaxhighlight> | ||
| + | |||
| + | Function bodies shall not contain blank lines. All statements within a function body are written without vertical separation. | ||
| + | |||
| + | == Braces == | ||
Braces are placed using a variant of the [http://wiki.c2.com/?OneTrueBraceStyle OTBS style]. The opening brace is placed on the same line as the declaring statement with one single space preceding it, and the closing brace is placed on a line of its own at the same level of indentation as the declaring statement. For if statements, the else/else if is placed on the same line as the closing brace. For do/while loops, the while is placed on the same line as the closing brace. | Braces are placed using a variant of the [http://wiki.c2.com/?OneTrueBraceStyle OTBS style]. The opening brace is placed on the same line as the declaring statement with one single space preceding it, and the closing brace is placed on a line of its own at the same level of indentation as the declaring statement. For if statements, the else/else if is placed on the same line as the closing brace. For do/while loops, the while is placed on the same line as the closing brace. | ||
| − | |||
<syntaxhighlight lang=c++ line=line> | <syntaxhighlight lang=c++ line=line> | ||
// Correct. | // Correct. | ||
| Line 280: | Line 250: | ||
</syntaxhighlight> | </syntaxhighlight> | ||
| − | ==Spacing== | + | == Spacing == |
The following are correct use cases for white spaces: | The following are correct use cases for white spaces: | ||
| Line 286: | Line 256: | ||
* Used for indentation. | * Used for indentation. | ||
* Used to surround binary operations. | * Used to surround binary operations. | ||
| + | |||
<syntaxhighlight lang=c++ line=line> | <syntaxhighlight lang=c++ line=line> | ||
// Correct. | // Correct. | ||
| Line 298: | Line 269: | ||
* One space is placed after a comma. | * One space is placed after a comma. | ||
| + | |||
<syntaxhighlight lang=c++ line=line> | <syntaxhighlight lang=c++ line=line> | ||
// Correct. | // Correct. | ||
| Line 308: | Line 280: | ||
</syntaxhighlight> | </syntaxhighlight> | ||
| − | =Syntax= | + | = Syntax = |
| − | ==Function Definitions== | + | == Function Definitions == |
Functions declared and defined in header files are formatted as follows: | Functions declared and defined in header files are formatted as follows: | ||
| + | |||
<syntaxhighlight lang=c++ line=line> | <syntaxhighlight lang=c++ line=line> | ||
inline int f() { | inline int f() { | ||
| Line 326: | Line 299: | ||
</syntaxhighlight> | </syntaxhighlight> | ||
| − | That is they are declared as inline unless they are function templates in which case the inline | + | That is they are declared as inline unless they are function templates in which case the inline specifier is omitted. |
| − | ==Variable Declarations== | + | == Variable Declarations == |
Variables are declared so that only one variable is declared per line, the type of the variable should [https://herbsutter.com/2013/08/12/gotw-94-solution-aaa-style-almost-always-auto/ almost always use auto], and variables should almost always be initialized. | Variables are declared so that only one variable is declared per line, the type of the variable should [https://herbsutter.com/2013/08/12/gotw-94-solution-aaa-style-almost-always-auto/ almost always use auto], and variables should almost always be initialized. | ||
| − | |||
<syntaxhighlight lang=c++ line=line> | <syntaxhighlight lang=c++ line=line> | ||
auto x = 5; | auto x = 5; | ||
| Line 349: | Line 321: | ||
</syntaxhighlight> | </syntaxhighlight> | ||
| − | = | + | == Class Member Declaration Order == |
| + | |||
| + | Within each access section (public, protected, private), declarations are ordered as follows: | ||
| + | |||
| + | # Nested types (enums, nested classes, typedefs, type aliases) | ||
| + | # Implicit constructors (compiler may call implicitly, e.g. non-explicit single parameter) | ||
| + | # Multi-parameter constructors (non-copy, non-move) | ||
| + | # Copy constructor, move constructor, destructor | ||
| + | # Member functions | ||
| + | # Operators (arithmetic, comparison, assignment) | ||
| + | |||
| + | Within a class, there must always be a single blank line between the destructor and the following declaration in the same access section. If there is no destructor, then a single blank line between the last constructor and the following declaration. | ||
| + | |||
| + | <syntaxhighlight lang=c++ line=line> | ||
| + | /** Base class for types that can be converted to a string. */ | ||
| + | class Stringable { | ||
| + | public: | ||
| + | |||
| + | /** Returns the string representation. */ | ||
| + | virtual std::string to_string() const = 0; | ||
| + | }; | ||
| + | |||
| + | /** Represents a duration of time. */ | ||
| + | class Duration : public Stringable { | ||
| + | public: | ||
| + | |||
| + | /** The underlying type used to count time units. */ | ||
| + | using Count = std::int64_t; | ||
| + | |||
| + | /** Returns a Duration from a count of milliseconds. */ | ||
| + | static Duration from_milliseconds(Count milliseconds); | ||
| + | |||
| + | /** Implicitly converts a count of seconds. */ | ||
| + | Duration(Count seconds); | ||
| + | |||
| + | /** Constructs a Duration from a string representation. */ | ||
| + | explicit Duration(std::string representation); | ||
| + | |||
| + | /** Constructs a Duration from a count and unit. */ | ||
| + | Duration(Count count, std::string unit); | ||
| + | |||
| + | Duration(const Duration& other); | ||
| + | Duration(Duration&& other) noexcept; | ||
| + | ~Duration() override; | ||
| + | |||
| + | /** Returns the count of time units. */ | ||
| + | Count get_count() const; | ||
| + | |||
| + | /** Returns true if this is a non-zero duration. */ | ||
| + | bool is_non_zero() const; | ||
| + | |||
| + | std::string to_string() const override; | ||
| + | |||
| + | Duration operator +(const Duration& rhs) const; | ||
| + | Duration operator -(const Duration& rhs) const; | ||
| + | |||
| + | bool operator ==(const Duration& rhs) const; | ||
| + | bool operator <(const Duration& rhs) const; | ||
| + | |||
| + | Duration& operator =(const Duration& rhs); | ||
| + | Duration& operator =(Duration&& rhs) noexcept; | ||
| + | }; | ||
| + | </syntaxhighlight> | ||
| + | |||
| + | Note how the copy constructor, move constructor, and destructor are undocumented and grouped without blank lines. The overridden ''to_string'' appears after the new method ''get_count''. Arithmetic, comparison, and assignment operators each form their own group at the end with no blank lines between them. | ||
| + | |||
| + | == Naming Conventions == | ||
| + | |||
| + | * Use [https://en.wikipedia.org/wiki/CamelCase CamelCase] for all type names including classes, structs, enums, type aliases, typedefs, and concepts. | ||
| + | * Use [https://en.wikipedia.org/wiki/Snake_case snake_case] for function and variable names. | ||
| + | * Type traits use snake_case to be consistent with the standard library convention. | ||
| + | * Do not use abbreviations for names. Variable names should be a single noun, dropping adjectives unless needed to disambiguate. | ||
| + | * Functions returning bool shall use a prefix that reads as a question: ''is_'', ''has_'', or ''can_''. | ||
| + | |||
| + | <syntaxhighlight lang=c++ line=line> | ||
| + | // Correct. | ||
| + | class OrderBook { | ||
| + | public: | ||
| + | auto get_price() const -> Money; | ||
| + | auto is_empty() const -> bool; | ||
| + | auto has_bid() const -> bool; | ||
| + | }; | ||
| + | |||
| + | template<typename T> | ||
| + | struct is_numeric; | ||
| + | |||
| + | auto order_count = 0; | ||
| + | |||
| + | // Incorrect: abbreviation, wrong case, missing question prefix. | ||
| + | class OrdBk { | ||
| + | public: | ||
| + | Money getPrice() const; | ||
| + | bool empty() const; | ||
| + | }; | ||
| + | |||
| + | auto orderCnt = 0; | ||
| + | </syntaxhighlight> | ||
| + | |||
| + | == Constructors and Operators == | ||
| + | |||
| + | Single parameter constructors shall be declared ''explicit'' to prevent implicit conversions. | ||
| + | |||
| + | <syntaxhighlight lang=c++ line=line> | ||
| + | // Correct. | ||
| + | class Distance { | ||
| + | public: | ||
| + | explicit Distance(int meters); | ||
| + | }; | ||
| + | |||
| + | // Incorrect, allows implicit conversion from int. | ||
| + | class Distance { | ||
| + | public: | ||
| + | Distance(int meters); | ||
| + | }; | ||
| + | </syntaxhighlight> | ||
| + | |||
| + | Constructors and assignment operators shall be declared ''noexcept'' if their implementations are actually noexcept. There is no need for ''noexcept'' on other methods. | ||
| + | |||
| + | <syntaxhighlight lang=c++ line=line> | ||
| + | class Connection { | ||
| + | public: | ||
| + | Connection(Connection&& other) noexcept; | ||
| + | Connection& operator =(Connection&& other) noexcept; | ||
| + | }; | ||
| + | </syntaxhighlight> | ||
| + | |||
| + | == Object Initialization == | ||
| + | |||
| + | Objects are always initialized using parentheses, never braces. The exception is when constructing a ''std::initializer_list''. | ||
| + | |||
| + | <syntaxhighlight lang=c++ line=line> | ||
| + | // Correct. | ||
| + | auto duration = Duration(5); | ||
| + | auto values = std::vector<int>(10, 0); | ||
| + | |||
| + | // Correct, std::initializer_list exception. | ||
| + | auto primes = std::vector<int>{2, 3, 5, 7, 11}; | ||
| + | |||
| + | // Incorrect. | ||
| + | auto duration = Duration{5}; | ||
| + | auto values = std::vector<int>{10, 0}; // Constructs {10, 0}, not 10 zeros. | ||
| + | </syntaxhighlight> | ||
| + | |||
| + | == Templates == | ||
| + | |||
| + | Use compile time argument deduction (CTAD) as much as possible. Provide deduction guides for all class templates unless a compiler generated deduction guide is available. | ||
| + | |||
| + | <syntaxhighlight lang=c++ line=line> | ||
| + | // Correct, CTAD with explicit deduction guide. | ||
| + | template<typename T> | ||
| + | class Wrapper { | ||
| + | public: | ||
| + | Wrapper(T value); | ||
| + | }; | ||
| + | |||
| + | template<typename T> | ||
| + | Wrapper(T) -> Wrapper<T>; | ||
| + | |||
| + | auto w = Wrapper(5); // Deduces Wrapper<int>. | ||
| + | |||
| + | // Incorrect, redundant template arguments. | ||
| + | auto w = Wrapper<int>(5); | ||
| + | </syntaxhighlight> | ||
| + | |||
| + | == Documentation == | ||
| + | |||
| + | Use modern jsdoc style comments. Do not use old Doxygen-style comments. Only document the interface, not the implementation. | ||
| + | |||
| + | <syntaxhighlight lang=c++ line=line> | ||
| + | // Correct. | ||
| + | /** Sends a message to all connected clients. */ | ||
| + | void broadcast(const std::string& message); | ||
| + | |||
| + | /** | ||
| + | * Connects to a server at the given host and port. | ||
| + | * @param host The hostname to connect to. | ||
| + | * @param port The port to connect on. | ||
| + | * @return The established connection. | ||
| + | */ | ||
| + | Connection connect(const std::string& host, int port); | ||
| + | |||
| + | // Incorrect, old Doxygen style. | ||
| + | /*! \brief Sends a message to all connected clients. */ | ||
| + | void broadcast(const std::string& message); | ||
| + | </syntaxhighlight> | ||
| + | |||
| + | == Character Set == | ||
| + | |||
| + | Only basic ASCII characters are permitted in source code. Never use unicode characters. | ||
| + | |||
| + | <syntaxhighlight lang=c++ line=line> | ||
| + | // Correct. | ||
| + | auto pi = 3.14159; | ||
| + | auto label = std::string("degrees"); | ||
| + | |||
| + | // Incorrect, unicode in source. | ||
| + | auto pi = 3.14159; // π | ||
| + | auto label = std::string("degrees °"); | ||
| + | </syntaxhighlight> | ||
| + | |||
| + | == Helper Functions == | ||
| + | |||
| + | When a helper function does not need access to class state, prefer placing it in the narrowest possible scope. In order of preference: | ||
| + | |||
| + | # Anonymous namespace (for file-local helpers in .cpp files) | ||
| + | # Free function in a Details namespace | ||
| + | # Static class member (only when needed) | ||
| + | |||
| + | <syntaxhighlight lang=c++ line=line> | ||
| + | // Preferred: anonymous namespace for file-local helpers. | ||
| + | namespace { | ||
| + | int compute_offset(int base) { | ||
| + | return base * 2 + 1; | ||
| + | } | ||
| + | } | ||
| + | |||
| + | // Acceptable: free function in Details namespace for header-only code. | ||
| + | namespace CppChat::Details { | ||
| + | inline int compute_offset(int base) { | ||
| + | return base * 2 + 1; | ||
| + | } | ||
| + | } | ||
| + | |||
| + | // Last resort: static class member. | ||
| + | class Connection { | ||
| + | public: | ||
| + | static int compute_offset(int base); | ||
| + | }; | ||
| + | </syntaxhighlight> | ||
| + | |||
| + | == Static Members == | ||
| + | |||
| + | Static class members shall always be accessed through the class name, never through an instance. | ||
| + | |||
| + | <syntaxhighlight lang=c++ line=line> | ||
| + | class Counter { | ||
| + | public: | ||
| + | static int instance_count; | ||
| + | }; | ||
| + | |||
| + | // Correct. | ||
| + | auto count = Counter::instance_count; | ||
| + | |||
| + | // Incorrect, accesses static member through an instance. | ||
| + | auto counter = Counter(); | ||
| + | auto count = counter.instance_count; | ||
| + | </syntaxhighlight> | ||
| + | |||
| + | = References = | ||
The bulk of this style guide focuses on syntax rather than good programming practices. The following list are reference materials (both online and published) for best practices on writing portable, efficient, and clean C++ code: | The bulk of this style guide focuses on syntax rather than good programming practices. The following list are reference materials (both online and published) for best practices on writing portable, efficient, and clean C++ code: | ||
| − | |||
* B. Stroustrup ''The C++ Programming Language (4th Edition)''. 2013 | * B. Stroustrup ''The C++ Programming Language (4th Edition)''. 2013 | ||
* [https://github.com/isocpp/CppCoreGuidelines Cpp Core Guidelines] | * [https://github.com/isocpp/CppCoreGuidelines Cpp Core Guidelines] | ||
| − | |||
* [https://cppcon.org/ CppCon - The C++ Conference] | * [https://cppcon.org/ CppCon - The C++ Conference] | ||
Latest revision as of 20:26, 27 May 2026
This article specifies the most general guidelines used for all Spire projects written using C++ as the primary programming language. Each individual project may extend or override the guidelines specified herein. The overall aim of this guideline is to provide consistency across the numerous projects developed by Spire in order to promote and benefit from modern C++ practices as well as tailor them to our specific requirements.
In general, the guidelines are very specific and opinionated. This is done intentionally to ensure that every detail has been given proper consideration and that every line of code is written with care and diligence. It is taken as a prerequisite that writing robust, clean and maintainable code requires paying close attention to even the smallest of details. As such, when there are two or more ways to write or express any given statement or expression, be it using spaces or tabs, number of characters per line, purple or violet, the guideline will often require that one particular approach be used to the exclusion of others.
Finally, this document is constantly evolving and as such older projects may not be up-to-date with the guidelines found here. In those circumstances one must use their best judgment about how to integrate these guidelines into older codebases. As a general principle, consistency should favor the local over the global, that is it is more important to be consistent with a function definition than within a file, and more important to be consistent within a file than within a directory, project, etc...
Contents
Relationship to the Cpp Core Guidelines
This document should be seen as an extension to the Cpp Core Guidelines authored and maintained by Herb Sutter and Bjarne Stroustrup. After reviewing these guidelines one should then become familiar with the Cpp Core Guidelines as documented here:
Those guidelines specify the best practices for writing modern C++ code. In situations where there is a conflict between the Cpp Core Guidelines and the guidelines set forth in this document, this document takes precedence. For all known situations where a conflict exists, this document should explicitly indicate that conflict to avoid confusion.
Development Environment
All projects target the C++23 standard. The two primary development environments supported by Spire projects are Linux and Windows. CMake is used to produce the appropriate project/make files for each platform.
For source control, git is used and projects are to be hosted on Spire Trading's Github page.
Each developer is welcome to use an editor of their choice.
File and Directory Naming
Use CamelCase for header and source file names, matching the primary type or class defined within. The main exception is for CMake files which must use the name CMakeLists.txt. Directory names use snake case with all lower case letters.
Project Structure
A project is broken down into one library component and one or more application components. The library is written in a header-only fashion, that is the implementation is provided directly in the header file rather than a separate .cpp source file. This allows for such libraries to easily be incorporated into other projects without the need for complex build configurations. Applications often use a mix of header only files and source files.
At the root of a project are the build scripts: build.bat/build.sh to build the project, configure.bat/configure.sh to produce the platform specific build files (VS solutions for Windows, make files for POSIX), and setup.bat/setup.sh to download all third party dependencies. Some projects may also include install scripts, version scripts, and scripts for continuous builds or deployment.
The library is organized into the following directories:
- Include - Header files (.hpp), organized by component under a project-level namespace directory.
- Source - Unit test source files (.cpp), organized into ComponentTests directories.
- Config - CMake build configuration files, with a subdirectory per component.
- Dependencies - Third party dependencies downloaded by the setup script.
Applications go into a separate Applications directory at the project root. Each application follows the same structure with its own Include, Source, Config, and Dependencies directories alongside its own build scripts.
The following example shows the structure of a project named TicTacToe consisting of a library with two components (Board and Rules), and two applications (TicTacToeConsole and TicTacToeUi):
TicTacToe # Root level directory.
build.bat # Builds all libraries and applications.
configure.bat # Produces platform specific build files.
setup.bat # Downloads all third party dependencies.
\Applications # Contains all applications.
\TicTacToeConsole # A console application.
build.bat # Application build scripts.
CMakeLists.txt # Top-level CMake config for the application.
\Config # CMake build files per component.
\TicTacToeConsole # CMake config for the main executable.
\Include # Application header files.
\TicTacToeConsole # Namespace directory.
\Source # Application source files.
\TicTacToeConsole # Source files for the application.
\TicTacToeUi # A GUI application.
\... # Same structure as TicTacToeConsole.
\TicTacToe # The main library.
build.bat # Library build scripts.
setup.bat # Downloads library dependencies.
CMakeLists.txt # Top-level CMake config for the library.
\Config # CMake build files per component.
\Board # CMake config for the Board component.
\Rules # CMake config for the Rules component.
\Include # Library header files.
\TicTacToe # Namespace directory.
\Board # Headers for the Board component.
\BoardTests # Test utility headers.
\Rules # Headers for the Rules component.
\Source # Library source and test files.
\BoardTests # Unit tests for the Board component.
\RulesTests # Unit tests for the Rules component.
\Dependencies # Third party dependencies.
Header File Structure
Include Guards
Header files are structured first with an include guard defined using SNAKE_CASE whose name consists of the project name appended to the directory name and finally the file name itself, ending with a _HPP suffix. As a note, all files end with a single new line character.
1 #ifndef CPP_CHAT_SERVER_HPP
2 #define CPP_CHAT_SERVER_HPP
3 ...
4 #endif
Include Directives
Next come the list of #include directives. Include files are ordered based on their category where the first category is standard C++ header files, the second category is external dependency header files, and finally the third category is project header files. Both standard and external dependency header files use angle brackets (#include <...>) and local project header files use quotes (#include "..."). Within each category files are listed in alphabetical order.
1 #include <tuple>
2 #include <vector>
3 #include <boost/noncopyable.hpp>
4 #include "cpp_chat/cpp_chat.hpp"
5 #include "cpp_chat/definitions.hpp"
Namespaces
After include directives the project's namespace is defined. All declarations and definitions must be contained within a namespace so as to avoid polluting the global namespace. Namespace definitions should be preceded by one single new line except when immediately following a namespace definition. The top level namespace is named after the project, and sub-namespaces may be used (although they should be used very sparingly). When possible, use the nested namespace syntax (A::B) instead of separate nested declarations.
1 namespace CppChat {
2 namespace SubChatA {
3 ...
4 }
5
6 namespace SubChatB::SubSubChatA {
7 ...
8 }
9 }
10
11 namespace CppChatExtra {
12 ...
13 }
One situation where nested namespaces are welcome and encouraged are for implementation details that do not form part of the namespace's public interface. These definitions are put in a nested namespace called Details and go into a file of their own whose name contains the suffix details. For example if the cpp_chat_server.hpp contains implementation details, then they should be included in the file cpp_chat_server_details.hpp and the definitions should go into the namespace CppChat::Details.
Layout
Indentation
Code is indented using 2 spaces per level, tabs are not permitted.
1 int factorial(int n) {
2 if(n == 0) {
3 return 1;
4 }
5 return n * factorial(n - 1);
6 }
Line Structure
Lines are limited to 80 characters. Exceptions to this rule are long include files and long string literals where breaking up the literal into multiple lines is not possible. In order to break up long statements into multiple lines, break the line after the earliest operator or punctuation mark (which includes commas, operators, and opening brackets).
1 // Correct, line break at comma.
2 f(a, ..., b,
3 c, ..., d);
4
5 // Incorrect, line break after expression.
6 f(a, ..., b
7 , c, ..., d);
8
9 // Correct, line break after operator.
10 a + ... + b +
11 c + ... + d;
12
13 // Incorrect, line break after expression.
14 a + ... + b
15 + c + ... + d;
16
17 // Correct, line break after opening bracket.
18 f(
19 a, ..., b, c, ..., d);
20
21 // Incorrect, line break after function name.
22 f
23 (a, ..., b, c, ..., d);
For statements that begin new blocks of code (such as if/while/class...), in order to avoid confusing the line continuation with the code block, the line continuation is indented two extra levels. For example consider the following snippet of code:
1 // Incorrect.
2 if(condition_a && condition_b && ... &&
3 condition_c) {
4 std::cout << "meow" << std::endl;
5 }
The line continuation on line 2 clashes with the code block on line 3. To avoid this clash the above code is indented as follows:
1 // Correct.
2 if(condition_a && condition_b && ... &&
3 condition_c) {
4 std::cout << "meow" << std::endl;
5 }
The extra level of indentation in the line continuation makes it clear where the condition ends and the code block begins.
Blank Lines
Documented declarations shall be separated from the following declaration by a blank line. Any two non-documented declarations shall not have a blank line between them. In general, public declarations shall be documented. This rule applies to all declarations including class members, free functions, and free variables.
1 class Connection {
2 public:
3
4 /** Constructs a Connection from a host and port. */
5 Connection(std::string host, int port);
6
7 Connection(Connection&& other) noexcept;
8 ~Connection();
9
10 /** Sends a message over the connection. */
11 void send(const std::string& message);
12
13 /** Returns true if the connection is open. */
14 bool is_open() const;
15 };
Function bodies shall not contain blank lines. All statements within a function body are written without vertical separation.
Braces
Braces are placed using a variant of the OTBS style. The opening brace is placed on the same line as the declaring statement with one single space preceding it, and the closing brace is placed on a line of its own at the same level of indentation as the declaring statement. For if statements, the else/else if is placed on the same line as the closing brace. For do/while loops, the while is placed on the same line as the closing brace.
1 // Correct.
2 if(<cond>) {
3 <body>
4 } else {
5 <default>
6 }
7
8 // Correct.
9 do {
10 <body>
11 } while(<cond>);
12
13 // Incorrect.
14 if(<cond>)
15 {
16 <body>
17 }
18 else
19 {
20 <default>
21 }
22
23 // Incorrect.
24 do
25 {
26 <body>
27 }
28 while(<cond>);
Spacing
The following are correct use cases for white spaces:
- Used for indentation.
- Used to surround binary operations.
1 // Correct.
2 auto x = a + b;
3 auto y = 5;
4
5 //! Incorrect
6 auto x = a+b;
7 auto y=5;
8 auto z= 5;
- One space is placed after a comma.
1 // Correct.
2 f(1, 2);
3 int g(int a, int b);
4
5 //! Incorrect
6 f(1,2);
7 int g(int a,int b);
Syntax
Function Definitions
Functions declared and defined in header files are formatted as follows:
1 inline int f() {
2 ...
3 return 123;
4 }
5
6 template<typename T>
7 bool g() {
8 ...
9 return false;
10 }
That is they are declared as inline unless they are function templates in which case the inline specifier is omitted.
Variable Declarations
Variables are declared so that only one variable is declared per line, the type of the variable should almost always use auto, and variables should almost always be initialized.
1 auto x = 5;
2 auto y = a + b;
For complex initialization of values, use an immediately invoked lambda expression as follows:
1 auto value = [&] {
2 if(condition) {
3 return 123;
4 }
5 return 321;
6 }();
Class Member Declaration Order
Within each access section (public, protected, private), declarations are ordered as follows:
- Nested types (enums, nested classes, typedefs, type aliases)
- Implicit constructors (compiler may call implicitly, e.g. non-explicit single parameter)
- Multi-parameter constructors (non-copy, non-move)
- Copy constructor, move constructor, destructor
- Member functions
- Operators (arithmetic, comparison, assignment)
Within a class, there must always be a single blank line between the destructor and the following declaration in the same access section. If there is no destructor, then a single blank line between the last constructor and the following declaration.
1 /** Base class for types that can be converted to a string. */
2 class Stringable {
3 public:
4
5 /** Returns the string representation. */
6 virtual std::string to_string() const = 0;
7 };
8
9 /** Represents a duration of time. */
10 class Duration : public Stringable {
11 public:
12
13 /** The underlying type used to count time units. */
14 using Count = std::int64_t;
15
16 /** Returns a Duration from a count of milliseconds. */
17 static Duration from_milliseconds(Count milliseconds);
18
19 /** Implicitly converts a count of seconds. */
20 Duration(Count seconds);
21
22 /** Constructs a Duration from a string representation. */
23 explicit Duration(std::string representation);
24
25 /** Constructs a Duration from a count and unit. */
26 Duration(Count count, std::string unit);
27
28 Duration(const Duration& other);
29 Duration(Duration&& other) noexcept;
30 ~Duration() override;
31
32 /** Returns the count of time units. */
33 Count get_count() const;
34
35 /** Returns true if this is a non-zero duration. */
36 bool is_non_zero() const;
37
38 std::string to_string() const override;
39
40 Duration operator +(const Duration& rhs) const;
41 Duration operator -(const Duration& rhs) const;
42
43 bool operator ==(const Duration& rhs) const;
44 bool operator <(const Duration& rhs) const;
45
46 Duration& operator =(const Duration& rhs);
47 Duration& operator =(Duration&& rhs) noexcept;
48 };
Note how the copy constructor, move constructor, and destructor are undocumented and grouped without blank lines. The overridden to_string appears after the new method get_count. Arithmetic, comparison, and assignment operators each form their own group at the end with no blank lines between them.
Naming Conventions
- Use CamelCase for all type names including classes, structs, enums, type aliases, typedefs, and concepts.
- Use snake_case for function and variable names.
- Type traits use snake_case to be consistent with the standard library convention.
- Do not use abbreviations for names. Variable names should be a single noun, dropping adjectives unless needed to disambiguate.
- Functions returning bool shall use a prefix that reads as a question: is_, has_, or can_.
1 // Correct.
2 class OrderBook {
3 public:
4 auto get_price() const -> Money;
5 auto is_empty() const -> bool;
6 auto has_bid() const -> bool;
7 };
8
9 template<typename T>
10 struct is_numeric;
11
12 auto order_count = 0;
13
14 // Incorrect: abbreviation, wrong case, missing question prefix.
15 class OrdBk {
16 public:
17 Money getPrice() const;
18 bool empty() const;
19 };
20
21 auto orderCnt = 0;
Constructors and Operators
Single parameter constructors shall be declared explicit to prevent implicit conversions.
1 // Correct.
2 class Distance {
3 public:
4 explicit Distance(int meters);
5 };
6
7 // Incorrect, allows implicit conversion from int.
8 class Distance {
9 public:
10 Distance(int meters);
11 };
Constructors and assignment operators shall be declared noexcept if their implementations are actually noexcept. There is no need for noexcept on other methods.
1 class Connection {
2 public:
3 Connection(Connection&& other) noexcept;
4 Connection& operator =(Connection&& other) noexcept;
5 };
Object Initialization
Objects are always initialized using parentheses, never braces. The exception is when constructing a std::initializer_list.
1 // Correct.
2 auto duration = Duration(5);
3 auto values = std::vector<int>(10, 0);
4
5 // Correct, std::initializer_list exception.
6 auto primes = std::vector<int>{2, 3, 5, 7, 11};
7
8 // Incorrect.
9 auto duration = Duration{5};
10 auto values = std::vector<int>{10, 0}; // Constructs {10, 0}, not 10 zeros.
Templates
Use compile time argument deduction (CTAD) as much as possible. Provide deduction guides for all class templates unless a compiler generated deduction guide is available.
1 // Correct, CTAD with explicit deduction guide.
2 template<typename T>
3 class Wrapper {
4 public:
5 Wrapper(T value);
6 };
7
8 template<typename T>
9 Wrapper(T) -> Wrapper<T>;
10
11 auto w = Wrapper(5); // Deduces Wrapper<int>.
12
13 // Incorrect, redundant template arguments.
14 auto w = Wrapper<int>(5);
Documentation
Use modern jsdoc style comments. Do not use old Doxygen-style comments. Only document the interface, not the implementation.
1 // Correct.
2 /** Sends a message to all connected clients. */
3 void broadcast(const std::string& message);
4
5 /**
6 * Connects to a server at the given host and port.
7 * @param host The hostname to connect to.
8 * @param port The port to connect on.
9 * @return The established connection.
10 */
11 Connection connect(const std::string& host, int port);
12
13 // Incorrect, old Doxygen style.
14 /*! \brief Sends a message to all connected clients. */
15 void broadcast(const std::string& message);
Character Set
Only basic ASCII characters are permitted in source code. Never use unicode characters.
1 // Correct.
2 auto pi = 3.14159;
3 auto label = std::string("degrees");
4
5 // Incorrect, unicode in source.
6 auto pi = 3.14159; // π
7 auto label = std::string("degrees °");
Helper Functions
When a helper function does not need access to class state, prefer placing it in the narrowest possible scope. In order of preference:
- Anonymous namespace (for file-local helpers in .cpp files)
- Free function in a Details namespace
- Static class member (only when needed)
1 // Preferred: anonymous namespace for file-local helpers.
2 namespace {
3 int compute_offset(int base) {
4 return base * 2 + 1;
5 }
6 }
7
8 // Acceptable: free function in Details namespace for header-only code.
9 namespace CppChat::Details {
10 inline int compute_offset(int base) {
11 return base * 2 + 1;
12 }
13 }
14
15 // Last resort: static class member.
16 class Connection {
17 public:
18 static int compute_offset(int base);
19 };
Static Members
Static class members shall always be accessed through the class name, never through an instance.
1 class Counter {
2 public:
3 static int instance_count;
4 };
5
6 // Correct.
7 auto count = Counter::instance_count;
8
9 // Incorrect, accesses static member through an instance.
10 auto counter = Counter();
11 auto count = counter.instance_count;
References
The bulk of this style guide focuses on syntax rather than good programming practices. The following list are reference materials (both online and published) for best practices on writing portable, efficient, and clean C++ code:
- B. Stroustrup The C++ Programming Language (4th Edition). 2013
- Cpp Core Guidelines
- CppCon - The C++ Conference
