Difference between revisions of "C++ Style Guide"
| Line 14: | Line 14: | ||
=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 Names= | =File Names= | ||
| − | Use [https://en.wikipedia.org/wiki/ | + | 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. |
Header files should use the extension ''hpp'' | Header files should use the extension ''hpp'' | ||
| Line 32: | Line 32: | ||
=Directory Structure= | =Directory Structure= | ||
| − | A project is broken down into one library component and one or more application components | + | 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: ''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 project directory contains: | |
| + | * ''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. | |
| − | + | build.sh | |
| − | + | configure.bat # Produces platform specific build files. | |
| − | + | configure.sh | |
| − | + | setup.bat # Downloads all third party dependencies. | |
| − | + | setup.sh | |
| − | + | \Applications # Contains all applications. | |
| − | + | \TicTacToeConsole # A console application. | |
| − | + | build.bat # Application build scripts. | |
| − | + | build.sh | |
| − | + | configure.bat | |
| − | + | configure.sh | |
| − | + | CMakeLists.txt # Top-level CMake config for the application. | |
| − | + | \Config # CMake build files per component. | |
| − | + | \TicTacToeConsole # CMake config for the main executable. | |
| − | + | CMakeLists.txt | |
| − | + | \Include # Application header files. | |
| − | \ | + | \TicTacToeConsole # Namespace directory. |
| − | \ | + | ConsoleRenderer.hpp |
| − | + | \Source # Application source files. | |
| − | + | \TicTacToeConsole # Source files for the application. | |
| − | + | ConsoleRenderer.cpp | |
| − | + | main.cpp | |
| − | + | \TicTacToeUi # A GUI application. | |
| − | \ | + | \... # Same structure as TicTacToeConsole. |
| − | + | \TicTacToe # The main library. | |
| − | + | build.bat # Library build scripts. | |
| − | + | build.sh | |
| − | \ | + | configure.bat |
| − | \... # | + | configure.sh |
| − | \ | + | setup.bat # Downloads library dependencies. |
| − | + | setup.sh | |
| − | + | CMakeLists.txt # Top-level CMake config for the library. | |
| − | + | \Config # CMake build files per component. | |
| − | + | \Board # CMake config for the Board component. | |
| − | + | CMakeLists.txt | |
| − | + | \Rules # CMake config for the Rules component. | |
| − | + | CMakeLists.txt | |
| − | + | \Include # Library header files. | |
| − | + | \TicTacToe # Namespace directory. | |
| − | + | \Board # Headers for the Board component. | |
| − | + | Board.hpp | |
| − | + | Cell.hpp | |
| − | + | \BoardTests # Test utility headers. | |
| − | + | TestBoard.hpp | |
| − | + | \Rules # Headers for the Rules component. | |
| − | + | MoveValidator.hpp | |
| − | + | WinChecker.hpp | |
| − | \ | + | \Source # Library source and test files. |
| − | + | \BoardTests # Unit tests for the Board component. | |
| − | + | BoardTester.cpp | |
| − | + | CellTester.cpp | |
| − | + | main.cpp | |
| − | + | \RulesTests # Unit tests for the Rules component. | |
| − | + | MoveValidatorTester.cpp | |
| − | + | WinCheckerTester.cpp | |
| − | \ | + | main.cpp |
| − | \ | + | \Dependencies # Third party dependencies. |
| − | |||
| − | |||
| − | \ | ||
| − | |||
| − | |||
| − | |||
| − | |||
| − | \ | ||
| − | |||
| − | |||
| − | |||
| − | |||
| − | \ | ||
| − | \ | ||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | \ | ||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
</pre> | </pre> | ||
| − | |||
| − | |||
=Header File Structure= | =Header File Structure= | ||
==Include Guard== | ==Include Guard== | ||
| − | Header files are structured first with an [https://en.wikipedia.org/wiki/Include_guard include guard] defined using | + | 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 | + | #ifndef CPP_CHAT_CHAT_SERVER_HPP |
| − | #define | + | #define CPP_CHAT_CHAT_SERVER_HPP |
... | ... | ||
#endif | #endif | ||
| Line 155: | Line 130: | ||
==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> | ||
| + | // Correct, uses nested namespace syntax. | ||
| + | namespace CppChat::Details { | ||
| + | ... | ||
| + | } | ||
| + | |||
namespace CppChat { | namespace CppChat { | ||
| − | |||
... | ... | ||
} | } | ||
| − | namespace | + | namespace CppChatExtra { |
| − | |||
... | ... | ||
| − | |||
| − | |||
} | } | ||
| − | namespace | + | // Incorrect, uses separate nested declarations. |
| + | namespace CppChat { | ||
| + | namespace Details { | ||
... | ... | ||
| + | } | ||
} | } | ||
</syntaxhighlight> | </syntaxhighlight> | ||
| Line 192: | Line 171: | ||
==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> | ||
| Line 243: | Line 220: | ||
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. | ||
| + | |||
| + | =Declaration Spacing= | ||
| + | |||
| + | 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. | ||
==Braces== | ==Braces== | ||
| Line 347: | Line 328: | ||
return 321; | return 321; | ||
}(); | }(); | ||
| + | </syntaxhighlight> | ||
| + | |||
| + | =Class Declaration Layout= | ||
| + | |||
| + | Within each access section (public, protected, private), declarations are ordered as follows: | ||
| + | |||
| + | # Nested types (enums, nested classes, typedefs, type aliases) | ||
| + | # Static member variables | ||
| + | # Static member functions | ||
| + | # Default constructor | ||
| + | # Implicit constructors (compiler may call implicitly, e.g. non-explicit single parameter) | ||
| + | # Explicit single parameter constructors | ||
| + | # Multi-parameter constructors (non-copy, non-move) | ||
| + | # Copy constructor | ||
| + | # Move constructor | ||
| + | # Destructor | ||
| + | # Conversion operators | ||
| + | # New (non-inherited) methods | ||
| + | # Inherited/overridden methods | ||
| + | # Arithmetic operators | ||
| + | # Comparison operators | ||
| + | # Assignment operators | ||
| + | |||
| + | 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 in the same access section. | ||
| + | |||
| + | <syntaxhighlight lang=c++ line=line> | ||
| + | /** Base class for types that can be converted to a string. */ | ||
| + | class Printable { | ||
| + | public: | ||
| + | virtual ~Printable() = default; | ||
| + | |||
| + | /** Returns a string representation. */ | ||
| + | virtual std::string to_string() const = 0; | ||
| + | }; | ||
| + | |||
| + | /** Represents a time duration. */ | ||
| + | class Duration : public Printable { | ||
| + | public: | ||
| + | |||
| + | /** Enumerates time units. */ | ||
| + | enum class Unit { | ||
| + | SECONDS, | ||
| + | MILLISECONDS | ||
| + | }; | ||
| + | |||
| + | /** The type used to store the count. */ | ||
| + | using Count = std::int64_t; | ||
| + | |||
| + | /** The zero duration constant. */ | ||
| + | static const Duration ZERO; | ||
| + | |||
| + | /** Returns a Duration from a count of milliseconds. */ | ||
| + | static Duration from_milliseconds(Count milliseconds); | ||
| + | |||
| + | /** Constructs a zero Duration. */ | ||
| + | Duration(); | ||
| + | |||
| + | /** 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, Unit unit); | ||
| + | |||
| + | Duration(const Duration& other); | ||
| + | Duration(Duration&& other) noexcept; | ||
| + | ~Duration() override; | ||
| + | |||
| + | /** Returns true if this is a non-zero duration. */ | ||
| + | explicit operator bool() const; | ||
| + | |||
| + | /** Returns the count in the given unit. */ | ||
| + | Count get_count(Unit unit) 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; | ||
| + | auto operator <=>(const Duration& rhs) const; | ||
| + | Duration& operator =(const Duration& rhs); | ||
| + | Duration& operator =(Duration&& rhs) noexcept; | ||
| + | Duration& operator +=(const Duration& rhs); | ||
| + | Duration& operator -=(const Duration& rhs); | ||
| + | |||
| + | private: | ||
| + | Count m_milliseconds; | ||
| + | }; | ||
| + | </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= | ||
| + | |||
| + | ==Types== | ||
| + | Use [https://en.wikipedia.org/wiki/CamelCase CamelCase] for all type names including classes, structs, enums, type aliases, typedefs, and concepts. | ||
| + | |||
| + | <syntaxhighlight lang=c++ line=line> | ||
| + | class ChatServer { ... }; | ||
| + | enum class ConnectionStatus { ... }; | ||
| + | using MessageList = std::vector<Message>; | ||
| + | template<typename T> | ||
| + | concept Serializable = ...; | ||
| + | </syntaxhighlight> | ||
| + | |||
| + | ==Functions and Variables== | ||
| + | Use [https://en.wikipedia.org/wiki/Snake_case snake_case] for function and variable names. | ||
| + | |||
| + | <syntaxhighlight lang=c++ line=line> | ||
| + | auto message_count = get_message_count(); | ||
| + | void send_message(const Message& message); | ||
| + | </syntaxhighlight> | ||
| + | |||
| + | ==Type Traits== | ||
| + | Type traits use snake_case to be consistent with the standard library convention. | ||
| + | |||
| + | <syntaxhighlight lang=c++ line=line> | ||
| + | template<typename T> | ||
| + | struct is_serializable { ... }; | ||
| + | </syntaxhighlight> | ||
| + | |||
| + | ==Class Fields== | ||
| + | Class member fields are prefixed with ''m_''. | ||
| + | |||
| + | <syntaxhighlight lang=c++ line=line> | ||
| + | class ChatServer { | ||
| + | private: | ||
| + | std::string m_name; | ||
| + | int m_port; | ||
| + | }; | ||
| + | </syntaxhighlight> | ||
| + | |||
| + | ==General Naming Rules== | ||
| + | Do not use abbreviations for names. Variable names should be a single noun, dropping adjectives unless needed to disambiguate. | ||
| + | |||
| + | <syntaxhighlight lang=c++ line=line> | ||
| + | // Correct. | ||
| + | auto connection = make_connection(); | ||
| + | auto server = find_server(); | ||
| + | |||
| + | // Incorrect, uses abbreviations. | ||
| + | auto conn = make_connection(); | ||
| + | auto srv = find_server(); | ||
| + | |||
| + | // Correct, adjective needed to disambiguate. | ||
| + | auto source_connection = get_source(); | ||
| + | auto destination_connection = get_destination(); | ||
| + | </syntaxhighlight> | ||
| + | |||
| + | =Constructors= | ||
| + | |||
| + | ==Explicit Constructors== | ||
| + | Single parameter constructors shall be declared ''explicit'' to prevent implicit conversions. | ||
| + | |||
| + | <syntaxhighlight lang=c++ line=line> | ||
| + | class Port { | ||
| + | public: | ||
| + | |||
| + | // Correct. | ||
| + | explicit Port(int value); | ||
| + | |||
| + | // Incorrect, allows implicit conversion from int. | ||
| + | Port(int value); | ||
| + | }; | ||
| + | </syntaxhighlight> | ||
| + | |||
| + | ==Noexcept== | ||
| + | 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; | ||
| + | |||
| + | // No noexcept needed on regular methods. | ||
| + | void send(const Message& message); | ||
| + | }; | ||
| + | </syntaxhighlight> | ||
| + | |||
| + | =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 server = ChatServer("localhost", 8080); | ||
| + | auto value = std::optional<int>(5); | ||
| + | |||
| + | // Incorrect, uses braces. | ||
| + | auto server = ChatServer{"localhost", 8080}; | ||
| + | auto value = std::optional<int>{5}; | ||
| + | |||
| + | // Exception: initializer_list. | ||
| + | auto values = std::vector<int>({1, 2, 3}); | ||
| + | </syntaxhighlight> | ||
| + | |||
| + | =Template Argument Deduction= | ||
| + | |||
| + | 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> | ||
| + | template<typename T> | ||
| + | class Wrapper { | ||
| + | public: | ||
| + | explicit Wrapper(T value); | ||
| + | |||
| + | private: | ||
| + | T m_value; | ||
| + | }; | ||
| + | |||
| + | // Deduction guide. | ||
| + | template<typename T> | ||
| + | Wrapper(T) -> Wrapper<T>; | ||
| + | |||
| + | // CTAD usage. | ||
| + | auto wrapper = Wrapper(42); | ||
| + | </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 Message& message); | ||
| + | |||
| + | /** | ||
| + | * Establishes a connection to the server. | ||
| + | * @param host The hostname to connect to. | ||
| + | * @param port The port number. | ||
| + | * @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 Message& message); | ||
| + | </syntaxhighlight> | ||
| + | |||
| + | =Character Set= | ||
| + | |||
| + | Only basic ASCII characters are permitted in source code. Never use unicode characters. | ||
| + | |||
| + | =Method Declaration Preference= | ||
| + | |||
| + | 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) | ||
| + | # Private static member function | ||
| + | # Private member function | ||
| + | # Protected static member function | ||
| + | # Protected member function | ||
| + | # Free function | ||
| + | # Public static member function | ||
| + | # Public member function | ||
| + | |||
| + | =Static Member Access= | ||
| + | |||
| + | Static class members shall always be accessed through the class name, never through an instance. | ||
| + | |||
| + | <syntaxhighlight lang=c++ line=line> | ||
| + | // Correct. | ||
| + | auto value = Money::ONE; | ||
| + | auto result = Connection::make_default(); | ||
| + | |||
| + | // Incorrect, accesses static member through an instance. | ||
| + | auto connection = Connection(); | ||
| + | auto result = connection.make_default(); | ||
| + | </syntaxhighlight> | ||
| + | |||
| + | =Boolean Function Naming= | ||
| + | |||
| + | Functions returning bool shall use a prefix that reads as a question: ''is_'', ''has_'', or ''can_''. | ||
| + | |||
| + | <syntaxhighlight lang=c++ line=line> | ||
| + | bool is_empty(const Inventory& inventory); | ||
| + | bool has_order(OrderId id) const; | ||
| + | bool can_connect() const; | ||
| + | </syntaxhighlight> | ||
| + | |||
| + | =Function Bodies= | ||
| + | |||
| + | Function bodies shall not contain blank lines. All statements within a function body are written without vertical separation. | ||
| + | |||
| + | <syntaxhighlight lang=c++ line=line> | ||
| + | // Correct. | ||
| + | void process(const Order& order) { | ||
| + | auto fields = order.get_fields(); | ||
| + | auto quantity = fields.m_quantity; | ||
| + | validate(fields); | ||
| + | submit(order); | ||
| + | } | ||
| + | |||
| + | // Incorrect, blank lines within function body. | ||
| + | void process(const Order& order) { | ||
| + | auto fields = order.get_fields(); | ||
| + | auto quantity = fields.m_quantity; | ||
| + | |||
| + | validate(fields); | ||
| + | |||
| + | submit(order); | ||
| + | } | ||
</syntaxhighlight> | </syntaxhighlight> | ||
Latest revision as of 13:47, 15 April 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
- 1 Core Guidelines
- 2 Development Environment
- 3 File Names
- 4 Directory Structure
- 5 Header File Structure
- 6 Layout
- 7 Declaration Spacing
- 8 Syntax
- 9 Class Declaration Layout
- 10 Naming Conventions
- 11 Constructors
- 12 Initialization
- 13 Template Argument Deduction
- 14 Documentation
- 15 Character Set
- 16 Method Declaration Preference
- 17 Static Member Access
- 18 Boolean Function Naming
- 19 Function Bodies
- 20 Additional References
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:
https://github.com/isocpp/CppCoreGuidelines
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 Names
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.
Header files should use the extension hpp
Source files should use the extension cpp
All files end with a single new line character.
Directory 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 project directory contains:
- 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.
build.sh
configure.bat # Produces platform specific build files.
configure.sh
setup.bat # Downloads all third party dependencies.
setup.sh
\Applications # Contains all applications.
\TicTacToeConsole # A console application.
build.bat # Application build scripts.
build.sh
configure.bat
configure.sh
CMakeLists.txt # Top-level CMake config for the application.
\Config # CMake build files per component.
\TicTacToeConsole # CMake config for the main executable.
CMakeLists.txt
\Include # Application header files.
\TicTacToeConsole # Namespace directory.
ConsoleRenderer.hpp
\Source # Application source files.
\TicTacToeConsole # Source files for the application.
ConsoleRenderer.cpp
main.cpp
\TicTacToeUi # A GUI application.
\... # Same structure as TicTacToeConsole.
\TicTacToe # The main library.
build.bat # Library build scripts.
build.sh
configure.bat
configure.sh
setup.bat # Downloads library dependencies.
setup.sh
CMakeLists.txt # Top-level CMake config for the library.
\Config # CMake build files per component.
\Board # CMake config for the Board component.
CMakeLists.txt
\Rules # CMake config for the Rules component.
CMakeLists.txt
\Include # Library header files.
\TicTacToe # Namespace directory.
\Board # Headers for the Board component.
Board.hpp
Cell.hpp
\BoardTests # Test utility headers.
TestBoard.hpp
\Rules # Headers for the Rules component.
MoveValidator.hpp
WinChecker.hpp
\Source # Library source and test files.
\BoardTests # Unit tests for the Board component.
BoardTester.cpp
CellTester.cpp
main.cpp
\RulesTests # Unit tests for the Rules component.
MoveValidatorTester.cpp
WinCheckerTester.cpp
main.cpp
\Dependencies # Third party dependencies.
Header File Structure
Include Guard
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_CHAT_SERVER_HPP
2 #define CPP_CHAT_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 // Correct, uses nested namespace syntax.
2 namespace CppChat::Details {
3 ...
4 }
5
6 namespace CppChat {
7 ...
8 }
9
10 namespace CppChatExtra {
11 ...
12 }
13
14 // Incorrect, uses separate nested declarations.
15 namespace CppChat {
16 namespace Details {
17 ...
18 }
19 }
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.
Declaration Spacing
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.
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.
Examples:
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 specified 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.
Examples of simple declarations:
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 Declaration Layout
Within each access section (public, protected, private), declarations are ordered as follows:
- Nested types (enums, nested classes, typedefs, type aliases)
- Static member variables
- Static member functions
- Default constructor
- Implicit constructors (compiler may call implicitly, e.g. non-explicit single parameter)
- Explicit single parameter constructors
- Multi-parameter constructors (non-copy, non-move)
- Copy constructor
- Move constructor
- Destructor
- Conversion operators
- New (non-inherited) methods
- Inherited/overridden methods
- Arithmetic operators
- Comparison operators
- Assignment operators
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 in the same access section.
1 /** Base class for types that can be converted to a string. */
2 class Printable {
3 public:
4 virtual ~Printable() = default;
5
6 /** Returns a string representation. */
7 virtual std::string to_string() const = 0;
8 };
9
10 /** Represents a time duration. */
11 class Duration : public Printable {
12 public:
13
14 /** Enumerates time units. */
15 enum class Unit {
16 SECONDS,
17 MILLISECONDS
18 };
19
20 /** The type used to store the count. */
21 using Count = std::int64_t;
22
23 /** The zero duration constant. */
24 static const Duration ZERO;
25
26 /** Returns a Duration from a count of milliseconds. */
27 static Duration from_milliseconds(Count milliseconds);
28
29 /** Constructs a zero Duration. */
30 Duration();
31
32 /** Implicitly converts a count of seconds. */
33 Duration(Count seconds);
34
35 /** Constructs a Duration from a string representation. */
36 explicit Duration(std::string representation);
37
38 /** Constructs a Duration from a count and unit. */
39 Duration(Count count, Unit unit);
40
41 Duration(const Duration& other);
42 Duration(Duration&& other) noexcept;
43 ~Duration() override;
44
45 /** Returns true if this is a non-zero duration. */
46 explicit operator bool() const;
47
48 /** Returns the count in the given unit. */
49 Count get_count(Unit unit) const;
50
51 std::string to_string() const override;
52
53 Duration operator +(const Duration& rhs) const;
54 Duration operator -(const Duration& rhs) const;
55 bool operator ==(const Duration& rhs) const;
56 auto operator <=>(const Duration& rhs) const;
57 Duration& operator =(const Duration& rhs);
58 Duration& operator =(Duration&& rhs) noexcept;
59 Duration& operator +=(const Duration& rhs);
60 Duration& operator -=(const Duration& rhs);
61
62 private:
63 Count m_milliseconds;
64 };
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
Types
Use CamelCase for all type names including classes, structs, enums, type aliases, typedefs, and concepts.
1 class ChatServer { ... };
2 enum class ConnectionStatus { ... };
3 using MessageList = std::vector<Message>;
4 template<typename T>
5 concept Serializable = ...;
Functions and Variables
Use snake_case for function and variable names.
1 auto message_count = get_message_count();
2 void send_message(const Message& message);
Type Traits
Type traits use snake_case to be consistent with the standard library convention.
1 template<typename T>
2 struct is_serializable { ... };
Class Fields
Class member fields are prefixed with m_.
1 class ChatServer {
2 private:
3 std::string m_name;
4 int m_port;
5 };
General Naming Rules
Do not use abbreviations for names. Variable names should be a single noun, dropping adjectives unless needed to disambiguate.
1 // Correct.
2 auto connection = make_connection();
3 auto server = find_server();
4
5 // Incorrect, uses abbreviations.
6 auto conn = make_connection();
7 auto srv = find_server();
8
9 // Correct, adjective needed to disambiguate.
10 auto source_connection = get_source();
11 auto destination_connection = get_destination();
Constructors
Explicit Constructors
Single parameter constructors shall be declared explicit to prevent implicit conversions.
1 class Port {
2 public:
3
4 // Correct.
5 explicit Port(int value);
6
7 // Incorrect, allows implicit conversion from int.
8 Port(int value);
9 };
Noexcept
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
6 // No noexcept needed on regular methods.
7 void send(const Message& message);
8 };
Initialization
Objects are always initialized using parentheses, never braces. The exception is when constructing a std::initializer_list.
1 // Correct.
2 auto server = ChatServer("localhost", 8080);
3 auto value = std::optional<int>(5);
4
5 // Incorrect, uses braces.
6 auto server = ChatServer{"localhost", 8080};
7 auto value = std::optional<int>{5};
8
9 // Exception: initializer_list.
10 auto values = std::vector<int>({1, 2, 3});
Template Argument Deduction
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 template<typename T>
2 class Wrapper {
3 public:
4 explicit Wrapper(T value);
5
6 private:
7 T m_value;
8 };
9
10 // Deduction guide.
11 template<typename T>
12 Wrapper(T) -> Wrapper<T>;
13
14 // CTAD usage.
15 auto wrapper = Wrapper(42);
Documentation
Use modern jsdoc style comments. Do not use old Doxygen-style comments. Only document the interface, not the implementation.
1 // Correct.
2
3 /** Sends a message to all connected clients. */
4 void broadcast(const Message& message);
5
6 /**
7 * Establishes a connection to the server.
8 * @param host The hostname to connect to.
9 * @param port The port number.
10 * @return The established connection.
11 */
12 Connection connect(const std::string& host, int port);
13
14 // Incorrect, old Doxygen style.
15
16 /*! \brief Sends a message to all connected clients. */
17 void broadcast(const Message& message);
Character Set
Only basic ASCII characters are permitted in source code. Never use unicode characters.
Method Declaration Preference
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)
- Private static member function
- Private member function
- Protected static member function
- Protected member function
- Free function
- Public static member function
- Public member function
Static Member Access
Static class members shall always be accessed through the class name, never through an instance.
1 // Correct.
2 auto value = Money::ONE;
3 auto result = Connection::make_default();
4
5 // Incorrect, accesses static member through an instance.
6 auto connection = Connection();
7 auto result = connection.make_default();
Boolean Function Naming
Functions returning bool shall use a prefix that reads as a question: is_, has_, or can_.
1 bool is_empty(const Inventory& inventory);
2 bool has_order(OrderId id) const;
3 bool can_connect() const;
Function Bodies
Function bodies shall not contain blank lines. All statements within a function body are written without vertical separation.
1 // Correct.
2 void process(const Order& order) {
3 auto fields = order.get_fields();
4 auto quantity = fields.m_quantity;
5 validate(fields);
6 submit(order);
7 }
8
9 // Incorrect, blank lines within function body.
10 void process(const Order& order) {
11 auto fields = order.get_fields();
12 auto quantity = fields.m_quantity;
13
14 validate(fields);
15
16 submit(order);
17 }
Additional 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:
- S. Meyers, Effective Modern C++. 2014
- B. Stroustrup The C++ Programming Language (4th Edition). 2013
- Cpp Core Guidelines
- Standard C++ Blog
- CppCon - The C++ Conference
