Difference between revisions of "C++ Style Guide"
(→Syntax) |
|||
Line 341: | Line 341: | ||
<syntaxhighlight lang=c++ line=line> | <syntaxhighlight lang=c++ line=line> | ||
− | auto value = [] { | + | auto value = [&] { |
if(condition) { | if(condition) { | ||
return 123; | return 123; |
Revision as of 17:54, 12 February 2018
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
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
The two primary development environments supported by Spire projects are Ubuntu Server 16.04 LTS and Windows 10. On Unix like systems the compiler of choice is g++ 5.4 and on Windows 10 it's Microsoft Visual Studio 2017 15.5.5. In order to support these two platforms, 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. Visual Studio 2017 is typically used for Windows and Visual Studio Code is typically used on Linux and Mac.
File Names
Use 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
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. These components are referred to as artifacts. 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.
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 make 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.
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.
Assuming a project named chat consisting of applications chat_server and chat_client sharing code common to both applications, the following directory structure should be used:
chat_project # Root level directory. \applications # Contains all applications. \chat_server # Contains the chat server application. \build # Files/scripts used to build the chat server. \config # Contains all CMake build files. CMakeLists.txt # The CMake config file for the overall application. \chat_server # Contains CMake build files for the main executable. \CMakeLists.txt # The CMake config file for the executable. \make # Contains scripts to build on POSIX systems. \build.sh # Script used to build the application. \run_cmake.sh # Script used to produce the make/build files. \set_env.sh # Script defining the environment variables. \version.sh # Script that produces the VERSION stamped header file. \windows # Contains scripts to build on Windows. \build.bat # Script used to build the application. \run_cmake.bat # Script used to produce the VS solution. \set_env.bat # Script defining environment variables. \version.bat # Script that produces the VERSION stamped header file. \source # Contains the C++ source files. \chat_server # Contains the file defining the main function. \main.cpp # Defines the main function. \source_dir_a # Contains additional source files specific the the server. \source_a.cpp # C++ source code files. \source_b.cpp \source_c.cpp \source_dir_b # Contains additional source files specific the the server. \source_d.cpp \source_e.cpp \source_f.cpp \chat_client # Contains the chat client application. \... # The structure is similar to the server application. \build # Contains build scripts to build the entire project. \make # Contains scripts to build on POSIX systems. \build.sh # Builds all libraries and applications. \local_build.sh # A helper script containing common build functions. \run_cmake.sh # Produces all project make/build files. \setup.sh # Installs all third party dependencies. \windows # Contains scripts to build on Windows. \build.bat # Builds all libraries and applications. \run_cmake.bat # Produces all VS solutions. \setup.bat # Installs all third party dependencies. \library # Contains the common library code. \build # Files/script used to build the library. \config # Contains all CMake build files. \CMakeLists.txt # The CMake config file for the library. \dir_a # Contains CMake build files for library component A. \CMakeLists.txt # The CMake config file to build component A and unit tests. \dir_b # Contains CMake build files for library component B. \CMakeLists.txt # The CMake config file to build component B and unit tests. \make # Contains scripts to build on POSIX systems. \build.sh # Script used to build the library. \run_cmake.sh # Script used to produce make/build files. \set_env.sh # Script defining the environment variables. \windows # Contains scripts to build on Windows. \build.bat # Script to build the library. \run_cmake.bat # Script to produce the VS solution. \set_env.bat # Script defining the environment variables. \include # Contains the library header files (.hpp) \chat_project # Same as above. \chat_project # Contains the top-most generic library header files. \chat_project.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) \chat_project # 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.
An example of the above directory structure containing a basic skeletal implementation of all files can be found here: https://github.com/spiretrading/chat_project
Header File Structure
Include Guard
Header files are structured first with an 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.
1 #ifndef CPP_CHAT_SERVER_HPP
2 #define CPP_CHAT_SERVER_HPP
3 ...
4 #endif
Include Directives
Next come the list of #include directives. Include files are ordered based on their category where the first category is standard C++ header files, the second category is external dependency header files, and finally the third category is project header files. Both standard and external dependency header files use angle brackets (#include <...>) and local project header files use quotes (#include "..."). Within each category files are listed in alphabetical order.
1 #include <tuple>
2 #include <vector>
3 #include <boost/noncopyable.hpp>
4 #include "cpp_chat/cpp_chat.hpp"
5 #include "cpp_chat/definitions.hpp"
Namespaces
After include directives the project's namespace is defined. All declarations and definitions must be contained within a namespace so as to avoid polluting the global namespace. Namespace definitions should be preceded by one single new line except when immediately following a namespace definition. The top level namespace is named after the project, and sub-namespaces may be used (although they should be used very sparingly).
1 namespace cpp_chat {
2 namespace sub_chat_a {
3 ...
4 }
5
6 namespace sub_chat_b {
7 namespace sub_sub_chat_a {
8 ...
9 }
10 }
11 }
12
13 namespace cpp_chat_extra {
14 ...
15 }
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 cpp_chat::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 the following rules are used:
- Break the line at a comma, operator or opening bracket.
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.
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 }();
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