Coding style cpp features - Godlike/Unicorn GitHub Wiki
- pointers and references
- variable initialization
- multiple variable declarations
- const placement
- auto keyword
- using directive and declaration
C++ has many features and the number is growing with every standard. This document covers the usage of certain c++ features and syntactic nuances in our project.
References and pointers express intent and lifetime control over an object. Consider the following points as hints on what shall be used when passing an object to a method:
- Shall the object be modified within a method, but the modifications shall not be visible to the caller - pass by value
- Shall the modifications on the object be visible to the caller - pass by reference
- Shall the object be used as-is and modifications shall be forbidden - pass by const reference
- The object is optional, nullable or resettable - pass by pointer (also consider const pointers) and check it against nullptr
- The object's lifetime shall be shared by the method - pass by
shared_ptr
Smart pointers shall be used not only to make sure dangling pointers are cleared, but also as a means to express the lifetime control over an object.
- Use raw pointers and references when passing non-owning references to the object
- Use raw pointers and references when the object outlives the referencing object or scope
- Use
shared_ptr
(and `weak_ptr) when sharing the ownership (lifetime) of the pointer - Use
unique_ptr
when transferring the lifetime control of the pointer
Always initialize variables upon declaration
C++ allows to declare multiple variables in one line reusing the same type for these variables. However such approach may lead to hiding the variable and intent of the declaration. Types and references/pointers to the type may be mixed in multiple variable declaration which makes it harder to process.
- Only use one declaration per line for class members
- Avoid using multiple declarations altogether;
int* pi, i; //! BAD: mixed types
int j, *pj; //! BAD: type and * are far apart
int m, n = 0; //! BAD: only n is initialized with 0
int i = 0;
int j = 0;
int* pi = nullptr; //! GOOD: one declaration per line
std::array<double, 3> ptA, ptB, ptC; //! BAD: type has a meaning and used for several properties
typedef std::array<double, 3> PointType; //! GOOD: typedef adds a meaning to the complex type
PointType ptA, ptB; //! OK: variables are using typedef but would be better off with one declaration per line
PointType ptC; //! GOOD: one declaration per line
In C++ const can be placed both to the left and to the right of desired variable, so the following are all valid placements:
const int zero = 0;
int const one = 1;
const char* str = "Hurr durr";
char const* s = "I'm a hoers";
const int& a = zero;
int const& b = one;
Any of the listed forms can be used. It is not a rule, but consider placing const
to the right of the type because it is more consistent with const pointers:
char const* const hello = "world";
If you're using auto
keyword, use it only when the actual type is explicitly visible or intuitive. auto
makes reviewing the code suboptimal if the type can't be deduced on spot.
The same approach applies to using IDEs - if your IDE shows it properly it does not mean other contributors are using it.
Avoid using auto
with return values of methods.
Example code
auto str = std::string("Hello world"); //! GOOD: explicit type
auto const limit = 0ul; //! GOOD: explicit type via literal
auto it = objects.begin(); //! OK: iterator (intuitive)
auto sum = values.sum(); //! BAD: masked type
Avoid using namespace
directive since it may lead to unintended ADL behavior. Use explicit namespace
scope to resolve method and type signatures.
If you want to introduce a shortcut to some specific method or type, consider using
declaration.
Do not introduce symbols to global namespace, place using
declarations within the scope where they are being used. Local using
declarations help to understand scope dependencies and make it easier to track these dependencies in future code maintenance.
Example code
using namespace godlike; //! BAD: may lead to ADL conflicts
using namespace unicorn; //! BAD: may lead to ADL conflicts
//! Snippet above introduces godlike::, unicorn:: and godlike::unicorn:: to ADL
using std::sin; //! BAD: introduces std::sin symbol to global namespace
{
using std::sin; //! OK: introduces std::sin symbol to parent scope
}
namespace godlike //! GOOD: explicit namespace scope
{
namespace unicorn //! GOOD: explicit namespace scope
{
}
}