Game engine behind Sea Dogs, Pirates of the Caribbean and Age of Pirates games.
Related articles: TBD
Note: This guideline is based on Collaborative Collection of C++ Best Practices
Consistency is the most important aspect of style. The second most important aspect is following a style that the average C++ programmer is used to reading.
C++ allows for arbitrary-length identifier names, so there’s no reason to be terse when naming things. Use descriptive names, and be consistent in the style.
This project uses .clang-format
file that specifies the formatting style. While this cannot help with naming, it is particularly important to maintain a consistent style.
MyClass
.MyMethod()
.k
prefix: const double kPi=3.14159265358979323;
.enum class MyEnum {VariantOne, VariantTwo}
)_
postfix to distinguish it from public data._
If you do, you risk colliding with names reserved for compiler and standard library implementation use:
class MyClass
{
public:
MyClass(int data) : data_(data)
{
}
int GetData() const
{
return data_;
}
private:
int data_;
};
Use the #pragma once
directive instead include guards which is quasi-standard across many compilers.
It’s short and makes the intent clear.
// Good Idea
// Requires no extra params and notifies the user that the file
// is a local file.
#include <string>
#include <storm/OtherLibHeader.hpp>
#include "MyLibHeader.hpp"
There is almost never a reason to declare an identifier in the global namespace. Instead, functions and classes should exist in an appropriately named namespace or in a class inside of a namespace. Identifiers which are placed in the global namespace risk conflicting with identifiers from other libraries (mostly C, which doesn’t have namespaces).
This project uses storm
namespace.
Ultimately this is a matter of preference, but .hpp and .cpp are widely recognized by various editors and tools. So the choice is pragmatic. Specifically, Visual Studio only automatically recognizes .cpp and .cxx for C++ files, and Vim doesn’t necessarily recognize .cc as a C++ file.
Some editors like to indent with tabs by default. This makes the code unreadable to anyone not using the exact same tab indentation settings. Configure your editor so this does not happen.
Raw memory access, allocation and deallocation, are difficult to get correct in C++ without risking memory errors and leaks. C++11 provides tools to avoid these problems.
// Bad Idea
MyClass *myobj = new MyClass;
// ...
delete myobj;
// Good Idea
auto myobj = std::make_unique<MyClass>(constructorParam1, constructorParam2); // C++14
auto myobj = std::unique_ptr<MyClass>(new MyClass(constructorParam1, constructoParam2)); // C++11
auto mybuffer = std::make_unique<char[]>(length); // C++14
auto mybuffer = std::unique_ptr<char[]>(new char[length]); // C++11
// or for reference counted objects
auto myobj = std::make_shared<MyClass>();
// ...
// myobj is automatically freed for you whenever it is no longer used.
std::array
or std::vector
Instead of C-style ArraysBoth of these guarantee contiguous memory layout of objects and can (and should) completely replace your usage of C-style arrays for many of the reasons listed for not using bare pointers.
Also, avoid using std::shared_ptr
to hold an array.
Exceptions cannot be ignored. Return values, such as using std::optional
, can be ignored and if not checked can cause crashes or memory errors. An exception, on the other hand, can be caught and handled. Potentially all the way up the highest level of the application with a log and automatic restart of the application.
Stroustrup, the original designer of C++, makes this point much better than I ever could.
Use the C++-style cast (static_cast<>, dynamic_cast<> …) instead of the C-style cast. The C++-style cast allows more compiler checks and is considerably safer.
// Bad Idea
double x = getX();
int i = (int) x;
// Not a Bad Idea
int i = static_cast<int>(x);
Additionally the C++ cast style is more visible and has the possibility to search for.
But consider refactoring of program logic (for example, additional checking on overflow and underflow) if you need to cast double
to int
. Measure three times and cut 0.9999999999981 times.
Compiler definitions and macros are replaced by the preprocessor before the compiler is ever run. This can make debugging very difficult because the debugger doesn’t know where the source came from.
// Bad Idea
#define PI 3.14159;
// Good Idea
namespace storm
{
class Constants
{
public:
// if the above macro would be expanded, then the following line would be:
// static const double 3.14159 = 3.14159;
// which leads to a compile-time error. Sometimes such errors are hard to understand.
static constexpr double PI = 3.14159;
};
}
These keywords make it clear to other developers how virtual functions are being utilized, can catch potential errors if the signature of a virtual function changes, and can possibly hint to the compiler of optimizations that can be performed.
std::filesystem
C++17 added a new filesystem
library which provides portable filesystem access across all supporting compilers
std::thread
C++11’s threading capabilities should be utilized over pthread
or WinThreads
.
Global data leads to unintended side effects between functions and can make code difficult or impossible to parallelize. Even if the code is not intended today for parallelization, there is no reason to make it impossible for the future.
Besides being global data, statics are not always constructed and deconstructed as you would expect. This is particularly true in cross-platform environments. See for example, this g++ bug regarding the order of destruction of shared static data loaded from dynamic modules.
std::shared_ptr
is “as good as a global” (http://stackoverflow.com/a/18803611/29975) because it allows multiple pieces of code to interact with the same data.
A singleton is often implemented with a static and/or shared_ptr
.
Exceptions which are thrown and captured internally during normal processing slow down the application execution. They also destroy the user experience from within a debugger, as debuggers monitor and report on each exception event. It is best to just avoid internal exception processing when possible.
Home | Site Map |