Skip to content

Commit

Permalink
Merge pull request #941 from CesiumGS/update-style-guide
Browse files Browse the repository at this point in the history
Update C++ style guide
  • Loading branch information
kring authored Sep 9, 2024
2 parents f602fcf + a252d98 commit a826502
Showing 1 changed file with 82 additions and 7 deletions.
89 changes: 82 additions & 7 deletions doc/style-guide.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,9 +22,9 @@ A change to C++ Core Guidelines [NL.17](http://isocpp.github.io/CppCoreGuideline
Use `clang-format` and the `.clang-format` configuration in the root of this repo. Easy. If you're using Visual Studio Code with the C/C++ extension installed, just run the "Format Document" command.

To format the source code from the command line, install node.js from [https://nodejs.org/](https://nodejs.org/), and run `npm install` in the project root directory.
Then, in order to apply the formatting, run `npm run format`.
Then, in order to apply the formatting, run `npm run format`.

We believe that source code should be readable and even beautiful, but that automated formatting is more important than either concern. Especially when readability is largely a matter of what we're used to, and beauty is mostly subjective. For our JavaScript / TypeScript code, we format our code with the ubiquitous [Prettier](https://prettier.io/) tool. In C++, clang-format is widely used but there's no de facto style, and so we've created a clang-format style that tracks as close to Prettier as possible. We will be able to get closer once this [clang-format patch](https://reviews.llvm.org/D33029) is merged.
We believe that source code should be readable and even beautiful, but that automated formatting is more important than either concern. Especially when readability is largely a matter of what we're used to, and beauty is mostly subjective. For our JavaScript / TypeScript code, we format our code with the ubiquitous [Prettier](https://prettier.io/) tool. In C++, clang-format is widely used but there's no de facto style, and so we've created a clang-format style that tracks as close to Prettier as possible.

## 📛 Naming

Expand All @@ -40,6 +40,8 @@ Our naming differs from the naming proposed in the C++ Core Guidelines, primaril
* DON'T prefix classes with `C`.
* DO prefix pure interfaces - a class with most methods virtual and no fields - with `I`.
* DON'T use Hungarian notation, except that it's useful to prefix pointer-like variables with a `p`, e.g. `pThing`. This is justified because it's not meant to convey type information (not so useful), but rather as an easily-understood abbreviation for the descriptive name of what the variable actually is (very useful). The variable isn't a `thing`, it's a pointer to a thing, but `pointerToThing` would get annoying quickly. (see [NL.5](http://isocpp.github.io/CppCoreGuidelines/CppCoreGuidelines#nl5-avoid-encoding-type-information-in-names))
* DO prefix optional values with `maybe`.
* DO prefix expected values with `expected`.
* DO prefix private fields with `_`, as in `_boundingVolume`.

## 💂‍♀️ Include Guards
Expand All @@ -48,6 +50,14 @@ A change to C++ Core Guidelines [SF.8](http://isocpp.github.io/CppCoreGuidelines

Use `#pragma once` at the top of header files rather than manual inclusion guards. Even though `#pragma once` is not technically ISO Standard C++, it _is_ supported everywhere and manual inclusion guards are really tedious. If platform-specific differences in `#pragma once` behavior are changing the meaning of our program, we may need to reconsider some other life choices (like dodgy use of symlinks), but our choice to use `#pragma once` likely remains sound.

## 📄 Forward Declarations

Not covered by C++ Core Guidelines.

* Forward declare types in our own libraries whenever you can. Only #include when you must.
* For third-party libraries, prefer to #include a fwd.h type of file if one is provided. #include the full implementation only when you must.
* If you find yourself writing complicated forward declarations for our own types or for third-party ones that don't include a fwd.h, consider making a fwd.h file for it.

## 🛑 Exceptions

A change to C++ Core Guidelines [I.10](http://isocpp.github.io/CppCoreGuidelines/CppCoreGuidelines#Ri-except) and [NR.3](http://isocpp.github.io/CppCoreGuidelines/CppCoreGuidelines#Rnr-no-exceptions).
Expand All @@ -57,17 +67,67 @@ cesium-native may be used in environments with exceptions disabled, such as in W
* Consistent with the C++ Core Guidelines, we should write exception-safe code. Nothing should break or leak when some other code throws an exception. Use RAII to handle resource cleanup.
* Don't throw exceptions as a result of input data that is bad in common and predictable ways. Loading a glTF should never throw, no matter how broken the glTF is. Instead, the possibility of bad data should be incorporated into the normal, non-exceptional glTF loading API.
* Don't allow third-party code to throw in expected use-cases, because that might cause immediate program termination in some contexts. In rare cases this might require changing a third-party library or selecting a different one.
* Report improper API usage and precondition violations with `assert` rather than by throwing exceptions. In CesiumJS, these kinds of checks would throw `DeveloperError` and would be removed from release builds. `assert` is a more elegant way to do much the same. The C++ Core Guidelines ([I.6](http://isocpp.github.io/CppCoreGuidelines/CppCoreGuidelines#i6-prefer-expects-for-expressing-preconditions) and [I.8](http://isocpp.github.io/CppCoreGuidelines/CppCoreGuidelines#i8-prefer-ensures-for-expressing-postconditions)) suggest using the `Expects` and `Ensures` macros from the Guidelines Support Library instead of `assert`, but we suggest sticking with the more standard `assert` for the time being.
* Report improper API usage and precondition violations with `CESIUM_ASSERT` rather than by throwing exceptions. In CesiumJS, these kinds of checks would throw `DeveloperError` and would be removed from release builds. `CESIUM_ASSERT` is a more elegant way to do much the same. The C++ Core Guidelines ([I.6](http://isocpp.github.io/CppCoreGuidelines/CppCoreGuidelines#i6-prefer-expects-for-expressing-preconditions) and [I.8](http://isocpp.github.io/CppCoreGuidelines/CppCoreGuidelines#i8-prefer-ensures-for-expressing-postconditions)) suggest using the `Expects` and `Ensures` macros from the Guidelines Support Library instead, but we suggest sticking with `CESIUM_ASSERT` for the time being.
* Don't cause buffer overruns or other memory corruption. If it's not possible to continue safely, throwing an exception can be ok. When exceptions are disabled, throwing an exception will cause immediate termination of the program, which is better than memory corruption.
* Functions should be declared `noexcept`.

## 🏷️ Run-Time Type Information

Not covered by C++ Core Guidelines.

cesium-native may be used in environments with RTTI disabled. Thus, use of RTTI, including `dynamic_cast`, is disallowed.

## ✨ Const

A change to C++ Core Guidelines [Con.1](https://isocpp.github.io/CppCoreGuidelines/CppCoreGuidelines#Rconst-immutable) and [Con.4](https://isocpp.github.io/CppCoreGuidelines/CppCoreGuidelines#con4-use-const-to-define-objects-with-values-that-do-not-change-after-construction).

In general we follow the advice suggested in https://quuxplusone.github.io/blog/2022/01/23/dont-const-all-the-things/:

* By default, make member functions `const` ([Con.2](https://isocpp.github.io/CppCoreGuidelines/CppCoreGuidelines#con2-by-default-make-member-functions-const)).
* By default, make pointers and references `const` ([Con.3](https://isocpp.github.io/CppCoreGuidelines/CppCoreGuidelines#con3-by-default-pass-pointers-and-references-to-consts)).
* Don't use `const` for local variables.
* Don't use `const` for by-value parameters.
* Don't use `const` for return types.
* Don't use `const` for data members ([C.12](https://isocpp.github.io/CppCoreGuidelines/CppCoreGuidelines#Rc-constref)).

## 🔀 Auto

A change to C++ Core Guidelines [ES.11](https://isocpp.github.io/CppCoreGuidelines/CppCoreGuidelines#Res-auto).

Avoid using `auto` except in the following cases where writing the full type is burdensome or impossible:

* Iterators
* Lambdas
* Views
* Structured bindings
* When the type is on the right hand side, e.g. casts

## ✨ Const by-value parameters
## 🤔 Optional and Expected

Not covered by the C++ Core Guidelines.

Whether a by-value parameter is `const`, e.g. `void someFunction(int foo)` versus `void someFunction(const int foo)` does not affect how that function is called in any way. From the standpoint of overload resolution, overriding, and linking, these two functions are _identical_. In fact, it is perfectly valid to _declare_ a function with a non-const value parameter and _define_ it with a const value parameter. Therefore we follow the advice suggested in https://abseil.io/tips/109:
* Use `->` and `*` instead of `value()` to access the contained value.
* Use the boolean operator instead of `has_value()`.

> 1. Never use top-level const on function parameters in declarations that are not definitions (and be careful not to copy/paste a meaningless const). It is meaningless and ignored by the compiler, it is visual noise, and it could mislead readers.
> 2. Do use top-level const on function parameters in definitions at your (or your team’s) discretion. You might follow the same rationale as you would for when to declare a function-local variable const.
## 🔢 Integers

Partly covered by C++ Core Guidelines.

* By default, use signed types ([ES.106](https://isocpp.github.io/CppCoreGuidelines/CppCoreGuidelines#Res-nonnegative)).
* By default, use fixed width integer types, e.g. `int32_t` instead of `int`.
* Use constructor notation for very safe integer conversions (where the range is checked) rather than `static_cast`.
* Use a safe form of index checking when accessing elements in standard library containers:

```cpp
if (meshId >= 0 && size_t(meshId) < model.meshes.size())
```

## 🔧 Utility Functions

Not covered by C++ Core Guidelines.

* Utility functions that are only in-use in their particular file should exist in an anonymous namespace.
* Utility functions that provide common functionality should be static member functions of a struct. This is preferred over a nested namespace. For example, see [GltfUtilities.h](../CesiumGltfContent/include/CesiumGltfContent/GltfUtilities.h).

## 🎱 Use UTF-8 Everywhere

Expand All @@ -80,6 +140,21 @@ We use UTF-8 everywhere, including on Windows where UTF-16 is the more common ap
* On Windows, when using Win32 and similar APIs, we must convert UTF-8 strings to UTF-16 and then call the wide character version of the system API (e.g. CreateFileW). Do this at the call site.
* Be careful when using the `fstream` API family on Windows. Make sure you read and understand [How to do text on Windows](https://utf8everywhere.org/#windows).

## ✅ Testing

Not covered by C++ Core Guidelines.

* Test files should be prefixed with `Test`, e.g. `TestModel` instead of `ModelTests`.

## 🗂️ Other

Not covered by C++ Core Guidelines.

* Use `this->` when accessing member variables and functions. This helps distinguish member variables from local variables and member functions from anonymous namespace functions.
* Static function definitions in a .cpp file should be preceded by `/*static*/`.
* Unused parameter names in callbacks should be commented out rather than omitted. Only use `[[maybe_unused]]` when you can't comment out parameter names, such as when parameters are conditionally accessed by `constexpr` logic.
* Use `[i]` instead of `.at(i)` for sequence containers like `std::vector` and `std::array`.

## 📚 Resources

* [Effective Modern C++](https://www.amazon.com/Effective-Modern-Specific-Ways-Improve/dp/1491903996) - Highly recommended reading to improve your use of the new features in C++11 and C++14.

0 comments on commit a826502

Please sign in to comment.