Programmers are picky about their code style. Extended debates on whether to use K&R style braces or GNU style braces, whether to use 2 or 4 spaces to indent has persisted on the internet. Style is important. Having a consistent style makes it easier to read code. Each company, and sometimes each project, often defines their own style leading to all sorts of inconsistencies in code. While there is no universally agreed style for C++, there are some widely accepted styles.
We are going to use clang-tidy
and clang-format
to make sure our code is consistent with modern programming practices. Both xclang-tidy
and clang-format
are installed in the CSS Linux Lab, but you may need to “activate” the “llvm-toolset” by adding the following command to your ~./bashrc
file. You can also manually execute this command, but we want to automate the mundane parts of software development, so we can focus on the more interesting issues.
$ source scl_source enable llvm-toolset-7.0
If you are using a C-Shell, please use the following command:
$ scl enable llvm-toolset-7.0 # This command is for c-shell users only. If you are using bash, ignore this.
clang-tidy
clang-tidy is part of the LLVM project https://clang.llvm.org/extra/index.html and it has 100s of different checks that it performs on the code. It can detect common errors like bugprone-infinite-loop, check for readability-else-after-return, warn about readability-function-size and many more common issues.
To produce high quality code, we need to examine each clang-tidy warning. In some cases, we might choose not to follow the warnings because it requires advanced features or the warning is intended for much large projects whereas our emphasis is easy on code that is easy to read and modify.
clang-tidy relies on .clang-tidy
configuration file to set its options. A sample .clang-tidy
file is below.
# Configuration options for clang-tidy # CSS Linux machines, Sep 2019: LLVM version 3.8.1 # # usage: clang-tidy *.cpp -- -std=c++14 # # --- # See https://clang.llvm.org/extra/clang-tidy/#using-clang-tidy for all possible checks Checks: '*,-fuchsia-*,-cppcoreguidelines-owning-memory,-cppcoreguidelines-pro-bounds-array-to-pointer-decay,-cppcoreguidelines-pro-bounds-pointer-arithmetic,-google-build-using-namespace,-hicpp-no-array-decay,-modernize-use-trailing-return-type,-llvm-header-guard,-cert-err60-cpp,-cppcoreguidelines-pro-bounds-constant-array-index,-google-global-names-in-headers,-cppcoreguidelines-avoid-magic-numbers,-readability-magic-numbers,cppcoreguidelines-avoid-c-arrays,-google-runtime-references,-cert-msc32-c,-cert-msc30-c,-cert-msc50-cpp,-cert-msc51-cpp,-clang-analyzer-security.insecureAPI.rand' WarningsAsErrors: '*' HeaderFilterRegex: '.*' CheckOptions: - { key: readability-identifier-naming.ClassCase, value: CamelCase } - { key: readability-identifier-naming.StructCase, value: CamelCase } - { key: readability-identifier-naming.EnumCase, value: CamelCase } - { key: readability-identifier-naming.GlobalConstantCase, value: UPPER_CASE } - { key: readability-identifier-naming.VariableCase, value: camelBack } - { key: readability-identifier-naming.ParameterCase, value: camelBack } - { key: readability-identifier-naming.PublicMemberCase, value: camelBack } # No good consensus on function names problem.isFinished() and GetInputFromUser() are both good # - { key: readability-identifier-naming.FunctionCase, value: camelBack } # - { key: readability-identifier-naming.PublicMethodCase, value: camelBack } # - { key: readability-identifier-naming.PrivateMethodCase, value: camelBack } ######################################################################## # Disabled checks ######################################################################## # -fuchsia-*, # Checks associated with fuchsia operating system # -cppcoreguidelines-owning-memory, # Using and learning about raw pointers, not type-based semantics of gsl::owner<T*> # -cppcoreguidelines-pro-bounds-array-to-pointer-decay, # Using pointers to arrays # -cppcoreguidelines-pro-bounds-pointer-arithmetic, # Not using <span> which is in C++20 # -google-build-using-namespace, # Will use "using namespace std" to make code easier to read # -hicpp-no-array-decay, # Using pointers to arrays # -modernize-use-trailing-return-type, # Not using the modern return type indications such as "factorial(int n) -> int" # -llvm-header-guard # Will use short header guards not full directory and file name # -cert-err60-cpp # Want to be able to throw exception with string # -cppcoreguidelines-pro-bounds-constant-array-index # Want to use array[index] without having to use gsl::at() # -google-global-names-in-headers # Readability, want to have "using namespace std;" in headers as well # -cppcoreguidelines-avoid-magic-numbers # When testing we use magic numbers # -readability-magic-numbers # When testing we use magic numbers # -google-runtime-references # Want to pass non-const references and modify them # -cert-msc32-c, cert-msc30-c # Using seed for rand for easy testing # -cert-msc50-cpp, cert-msc51-cpp, clang-analyzer-security.insecureAPI.rand # Using rand() rather than random library, less random but easier
Checks
– Each check can be enabled or disables. We enable all checks using “*” and then disable some of them with a “-” before the check name. The reason for disabling each of the checks is explained at the bottom of the .clang-tidy file in comments. We want to enable as many checks as possible.
WarningsAsErrors
– Will treat warnings as errors, so we can use the return code from clang-tidy to detect if any warnings were issued
HeaderFilterRegex
– display errors from all non-system headers
CheckOptions
– Forces all Class names to be CamelCase, variables to be camelBack, globals to be UPPER_CASE.
We can execute clang-tidy as below:
$ clang-tidy *.cpp -- -std=c++14 11211 warnings generated. 22429 warnings generated. factorial.cpp:13:14: error: invalid case style for parameter 'N' [readability-identifier-naming,-warnings-as-errors] int fact(int N) { ^~ n factorial.cpp:16:16: error: statement should be inside braces [google-readability-braces-around-statements,-warnings-as-errors] if (N <= 1) ^ Suppressed 22418 warnings (22418 in non-user code). Use -header-filter=.* to display errors from all non-system headers. Use -system-headers to display errors from system headers as well. 9 warnings treated as errors
The “suppressed warnings” are from system libraries and can be ignored.
clang-tidy also has a -fix
option that should be used with care. It will attempt to fix the file. Making a backup before trying to fix it is strongly recommended. After fixing the file, you can view it in VSC in the “source control” view to see what has changed before committing them to your repository.
$ clang-tidy -fix *.cpp -- -std=c++14
clang-format
clang-format can be used as a standalone program or as integrated into VSC to format your document https://clang.llvm.org/docs/ClangFormat.html
clang-format relies on .clang-format
configuration file. This file can be generated using clang-format -style=llvm -dump-config > .clang-format
The .Clang-format
file specifies how braces are aligned, whether single line short functions are allowed, whether you need space before parenthesis in an if statement, etc.
From the command line, you can run clang-format -i factorial.cpp
which will modify the factorial.cpp file to conform to the formatting instructions. If you’d like to see how clang-format
will modify your source files, you can use clang-format factorial.cpp | diff factorial.cpp -
to see which lines will get modified. Unlike clang-tidy,
letting clang-format
modify program files is much safer.
To make sure VSC is using clang-format to format the document as you write your program.
- View > Command Palette > Format Document With …
- Configure Default Formatter
- Choose “clang-format”
You can now use “Format Document” (Shift + Alt + F
on Windows, Shift + Option + F
on Mac, Ctrl + Shift + I
on Linux) to format your document based on the defined style.