Difference between revisions of "C++ Style Guide"

From Spire Trading Inc.
Jump to: navigation, search
 
Line 14: Line 14:
 
=Development Environment=
 
=Development Environment=
  
The two primary development environments supported by Spire projects are Ubuntu Server 18.04 LTS and Windows 10. On POSIX systems the compiler of choice is g++ 7.3 and on Windows 10 it's Microsoft Visual Studio 2017 15.7.5. In order to support these two platforms, CMake is used to produce the appropriate project/make files for each platform.
+
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. [https://www.visualstudio.com/downloads/ Visual Studio 2017] is typically used for Windows and [https://code.visualstudio.com/ Visual Studio Code] is typically used on Linux and Mac.
+
Each developer is welcome to use an editor of their choice.
  
 
=File Names=
 
=File Names=
  
Use [https://en.wikipedia.org/wiki/Snake_case snake case] using all lower case letters for files and directories. The main exception is for CMake files which must use the name ''CMakeLists.txt''
+
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. These components are referred to as ''artifacts''. 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.
+
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.
  
The applications go into their own ''applications'' directory and the libraries into the ''library'' directory. Within each artifact is a ''build'' directory containing the build scripts and CMake files needed to build that artifact. Scripts for POSIX systems are found in the ''posix'' sub-directory and Windows build files are within the ''windows'' sub-directory. The build scripts specified are ''run_cmake'' to produce the platform specific build files (make files for POSIX and VS solutions for Windows), ''version'' to produce a header file that contains the version of the artifact produced and the ''build'' script which produces the artifact. In order to build an artifact, one first produces the build files for their platform by running the ''run_cmake'' script, and then they can run the ''build'' script afterwards to produce the artifact.
+
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.
  
Finally there is a top-level ''build'' directory for the project as a whole which produces all artifacts by recursively calling each artifact's individual build scripts. Additionally the top-level build directory also includes a ''setup'' script which downloads all third party dependencies. Some projects may also opt to include an ''install'' script which sets up a suitable development environment, and other scripts for continuous builds, deployment and testing.
+
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.
  
Assuming a project named tic_tac_toe consisting of applications tic_tac_toe_console and tic_tac_toe_ui sharing code common to both applications, the following directory structure should be used:
+
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>
tic_tac_toe                        # Root level directory.
+
TicTacToe                          # Root level directory.
   \applications                    # Contains all applications.
+
   build.bat                        # Builds all libraries and applications.
    \tic_tac_toe_console          # Contains the source for a console application.
+
  build.sh
      \build                      # Files/scripts used to build the console tic-tac-toe game.
+
  configure.bat                   # Produces platform specific build files.
        \config                   # Contains all CMake build files.
+
  configure.sh
          CMakeLists.txt          # The CMake config file for the overall application.
+
  setup.bat                        # Downloads all third party dependencies.
          \tic_tac_toe_console    # Contains CMake build files for the main executable.
+
  setup.sh
            \CMakeLists.txt        # The CMake config file for the executable.
+
  \Applications                    # Contains all applications.
        \posix                    # Contains scripts to build on POSIX systems.
+
    \TicTacToeConsole              # A console application.
          \build.sh                # Script used to build the application.
+
      build.bat                    # Application build scripts.
          \run_cmake.sh            # Script used to produce the make/build files.
+
      build.sh
          \set_env.sh             # Script defining the environment variables.
+
      configure.bat
          \version.sh             # Script that produces the VERSION stamped header file.
+
      configure.sh
        \windows                  # Contains scripts to build on Windows.
+
      CMakeLists.txt               # Top-level CMake config for the application.
          \build.bat               # Script used to build the application.
+
       \Config                     # CMake build files per component.
          \run_cmake.bat          # Script used to produce the VS solution.
+
         \TicTacToeConsole          # CMake config for the main executable.
          \set_env.bat            # Script defining environment variables.
+
           CMakeLists.txt
          \version.bat            # Script that produces the VERSION stamped header file.
+
      \Include                    # Application header files.
       \source                     # Contains the C++ source files.
+
        \TicTacToeConsole          # Namespace directory.
         \tic_tac_toe_console      # Contains the file defining the main function.
+
           ConsoleRenderer.hpp
           \main.cpp                # Defines the main function.
+
      \Source                      # Application source files.
        \source_dir_a              # Contains additional source files specific the console.
+
         \TicTacToeConsole          # Source files for the application.
          \source_a.cpp            # C++ source code files.
+
           ConsoleRenderer.cpp
           \source_b.cpp
+
           main.cpp
          \source_c.cpp
+
     \TicTacToeUi                  # A GUI application.
         \source_dir_b              # Contains additional source files specific the console.
+
       \...                        # Same structure as TicTacToeConsole.
           \source_d.cpp
+
   \TicTacToe                      # The main library.
           \source_e.cpp
+
     build.bat                      # Library build scripts.
          \source_f.cpp
+
    build.sh
     \tic_tac_toe_ui                # Contains a GUI version of tic-tac-toe.
+
     configure.bat
       \...                        # The structure is similar to the console application.
+
    configure.sh
   \build                          # Contains build scripts to build the entire project.
+
    setup.bat                     # Downloads library dependencies.
     \posix                        # Contains scripts to build on POSIX systems.
+
    setup.sh
      \build.sh                    # Builds all libraries and applications.
+
     CMakeLists.txt                 # Top-level CMake config for the library.
      \local_build.sh              # A helper script containing common build functions.
+
    \Config                        # CMake build files per component.
      \run_cmake.sh                # Produces all project make/build files.
+
      \Board                      # CMake config for the Board component.
      \setup.sh                   # Installs all third party dependencies.
+
         CMakeLists.txt
     \windows                      # Contains scripts to build on Windows.
+
       \Rules                       # CMake config for the Rules component.
      \build.bat                   # Builds all libraries and applications.
+
         CMakeLists.txt
      \run_cmake.bat              # Produces all VS solutions.
+
     \Include                       # Library header files.
      \setup.bat                   # Installs all third party dependencies.
+
       \TicTacToe                  # Namespace directory.
  \library                        # Contains the common library code.
+
         \Board                     # Headers for the Board component.
     \build                        # Files/script used to build the library.
+
           Board.hpp
      \config                      # Contains all CMake build files.
+
           Cell.hpp
        \CMakeLists.txt           # The CMake config file for the library.
+
        \BoardTests                # Test utility headers.
        \dir_a                    # Contains CMake build files for library component A.
+
           TestBoard.hpp
          \CMakeLists.txt          # The CMake config file to build component A and unit tests.
+
         \Rules                    # Headers for the Rules component.
         \dir_b                    # Contains CMake build files for library component B.
+
           MoveValidator.hpp
          \CMakeLists.txt         # The CMake config file to build component B and unit tests.
+
           WinChecker.hpp
       \posix                       # Contains scripts to build on POSIX systems.
+
     \Source                       # Library source and test files.
        \build.sh                  # Script used to build the library.
+
       \BoardTests                  # Unit tests for the Board component.
         \run_cmake.sh              # Script used to produce make/build files.
+
         BoardTester.cpp
        \set_env.sh                # Script defining the environment variables.
+
         CellTester.cpp
      \windows                    # Contains scripts to build on Windows.
+
         main.cpp
        \build.bat                # Script to build the library.
+
       \RulesTests                  # Unit tests for the Rules component.
        \run_cmake.bat            # Script to produce the VS solution.
+
         MoveValidatorTester.cpp
        \set_env.bat              # Script defining the environment variables.
+
         WinCheckerTester.cpp
     \include                       # Contains the library header files (.hpp)
+
         main.cpp
       \tic_tac_toe
+
    \Dependencies                 # Third party dependencies.
        \tic_tac_toe              # Contains the top-most generic library header files.
 
          \tic_tac_toe.hpp        # Header file containing generic forward declarations.
 
         \dir_a                     # Contains header files for library component A.
 
           \a.hpp                   # Header file containing forward declarations for component A.
 
           \a_header_1.hpp         # Header file for library component A.
 
          \a_header_2.hpp
 
           \a_header_3.hpp
 
         \dir_b
 
          \b.hpp                  # Header file containing forward declarations for component B.
 
          \b_header_1.hpp          # Contains header files for library component B.
 
           \b_header_2.hpp
 
           \b_header_3.hpp
 
     \source                       # Contains the library source files (.cpp)
 
       \tic_tac_toe                # Dummy directory for the overall library.
 
        \dummy.cpp                # Dummy source code file.
 
      \dir_a                      # Dummy directory for library component A.
 
        \dummy.cpp                # Dummy source file.
 
      \dir_a_tester                # Contains unit tests for library component A.
 
         \a_header_1_tester.cpp    # Contains unit tests for definitions in a_header_1.hpp
 
        \a_header_2_tester.cpp
 
         \a_header_3_tester.cpp
 
         \main.cpp                 # The entry point for component A unit tests.
 
       \dir_b                      # Dummy directory for library component B.
 
        \dummy.cpp                # Dummy source file.
 
      \dir_b_tester                # Contains unit tests for library component B.
 
         \b_header_1_tester.cpp     # Contains unit tests for definitions in b_header_a.hpp
 
         \b_header_2_tester.cpp
 
         \b_header_3_tester.cpp
 
        \main.cpp                 # The entry point for component B unit tests.
 
 
</pre>
 
</pre>
 
An example of the above directory structure containing a basic skeletal implementation of all files can be found here: https://github.com/spiretrading/tic_tac_toe
 
  
 
=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 capital letter snake case whose name consists of the project name appended to the directory name and finally the file name itself. As a note, all files end with a single new line character.
+
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_CHAT_SERVER_HPP
#define CPP_CHAT_SERVER_HPP
+
#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 SubChatA {
 
 
   ...
 
   ...
 
}
 
}
  
namespace SubChatB {
+
namespace CppChatExtra {
namespace SubSubChatA {
 
 
   ...
 
   ...
}
 
}
 
 
}
 
}
  
namespace CppChatExtra {
+
// 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 following rules are used:
+
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).
 
 
* Break the line at a comma, operator or opening bracket.
 
 
<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...

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:

  1. Nested types (enums, nested classes, typedefs, type aliases)
  2. Static member variables
  3. Static member functions
  4. Default constructor
  5. Implicit constructors (compiler may call implicitly, e.g. non-explicit single parameter)
  6. Explicit single parameter constructors
  7. Multi-parameter constructors (non-copy, non-move)
  8. Copy constructor
  9. Move constructor
  10. Destructor
  11. Conversion operators
  12. New (non-inherited) methods
  13. Inherited/overridden methods
  14. Arithmetic operators
  15. Comparison operators
  16. 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:

  1. Anonymous namespace (for file-local helpers in .cpp files)
  2. Private static member function
  3. Private member function
  4. Protected static member function
  5. Protected member function
  6. Free function
  7. Public static member function
  8. 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: