-
Classes and structs should be formatted and laid out in a consistent order, such as:
class Point { public: Point(); Point(double x, double y); Point(const Point&) = default; Point& operator=(const Point&) = default; Point(Point&&) = default; Point& operator=(Point&&) = default; double distance(const Point& other); private: double x_; // private member variables come before double y_; // any private member functions double internal_computation(); }; Point::Point(double x, double y) : x_(x), y_(y) // initializer list on separate line { } inline // inline keyword is at definition double Point::distance(const Point& other) { // ... }
Prefer this ordering of elements (not all elements have to be present in every class):
- Constructors, in order of increasing complexity (e.g., default constructor first);
- Destructor;
- Copy & move constructors and assignment operators;
- Public member variables (only if POD);
- Public member functions;
- Protected member variables (avoid, if possible);
- Protected member functions;
- Private member variables;
- Private member functions.
The initializer list for constructor definitions should always be placed on a separate line, to enhance readability.
-
Strongly aim to declare all member variables
private, to increase information hiding. Try to prevent designing classes with part of their member variablesprivateand otherspublic.privatemember variables allow invariants and other checks to be enforced via getter/setter functions, which greatly contributes to safer code. -
Place only functions into a
protectedsection, never any member variables. In case you need to access the data of members in a derived class, do it viaprotectedgetter/setter functions in the base class. -
Only use structs with
publicdata members for plain data storage (of all members in the respectivestruct). Never make variablespublicwhen there is even the slightest risk that the user can modify these variables in such a way that the internal state becomes inconsistent.Example: (Do not do this; instead write an interface that cannot be used incorrectly.)
struct Grayscale_image { unsigned char* data; // anyone can let this point to std::size_t size; // another memory location or change // the size to an inconsistent value // ... };
- Rationale [for the three points above]: Declaring data members
privateallows syntactically uniform access to data, fine-grained access control, and provides implementation flexibility (i.e. the internals can change, but the interface stays constant). See also Scott Meyers, “Effective C++”, 3rd Edition, Item 22.
- Rationale [for the three points above]: Declaring data members
-
The order of access modifiers should be
public, thenprotected, thenprivate.-
The
publicinterface should come first, so that everyone can quickly see how to use a class. Do not “hide” thepublicinterface by putting theprivateorprotectedsections first. Consistency is crucial to ensure readability. -
Avoid any repetition of access modifiers; e.g. a
privatesection, followed by apublicsection, followed by aprivatesection.
-
-
Ensure that all non-POD classes and structs have proper constructors. Structs where a user has to explicitly assign the
structmembers are considered bad design.Bad example:
struct Point // where is the constructor? { float x; // missing float y; // value float z; // initialization }; Point p; // Oops, uninitialized memory here! p.x = 0.0f; // Oops, forgot to assign to z. This should p.y = 0.0f; // not even be possible with proper class design.
-
In constructor definitions, aim to initialize all variables via the initializer list. Explicit assignment inside the constructor is only allowed in cases where initialization via the initializer list is impossible.
Example:
Foo::Foo(int var0, double var1, Widget var2) : var0_(var0), var1_(var1), var2_(var2) { // var2_ = var2; *avoid* }
- Explicit assignment inside the constructor leads to default construction of the member followed by re-assignment, which is often more inefficient than direct construction.
-
Class member variables have to be initialized in constructor initializer lists in exactly the same order as they are declared in the class. This avoids potentially wrong initializations (and compiler warnings about it).
-
Always make sure that all member variables of a class are properly initialized in every constructor.
Non-trivial types with default constructors often do not need to be listed explicitly should they be default constructed, but never leave built-in types such as integers or floating point numbers uninitialized.
-
Prefer to explicitly list the copy constructor, the copy assignment operator, the move constructor as well as the move assignment operator, even if they are just defaulted or deleted (as in the example above).
- The above mentioned constructors/operators can be default generated (without having to be mentioned in code) as long as the class does not have a user-defined destructor. However, for reasons of code clarity, it is greatly preferable to explicitly state the intent (e.g. defaulted, deleted, or custom implementation). See also Scott Meyers, “Effective Modern C++”, Item 17.
-
privateorprotectedmember variables should be suffixed with an underscore (_), to distinguish them frompublicmember and non-member variables.publicmember variables should have no such suffix. Never use a prefix underscore for any variable definition.- The suffix underscore is quick and easy to type, and many C++ experts are using this style. It's a unique visual identifier for a (private or protected) member variable.
- Prefix constructs like
m_indexlook too much like Hungarian notation, which is discouraged. Besides, it’s harder to type and looks uglier. - Prefix underscores are not allowed by the standard in many cases (e.g.
__, or_followed by an uppercase letter), so it’s better to not be tempted to use them.
-
Cleanly separate member function declaration (inside a class) from member function definition (outside the class, and in a separate translation unit, if not a class template). Exceptions can be made for trivial getter/setter functions, or for very small class implementations.
Example:
class Foo { int bar(int x); }; int Foo::bar(int x) { // ... (non-trivial implementation) }
Not:
class Foo { int bar(int x) { // ... (non-trivial implementation) } };
- The class definition provides the interface that a “user” of the class can refer to and obtain a quick summary from. For this reason, it should not consist of a lot of implementation.
-
Methods in translation units should be defined in the same order as declared in header files. This greatly simplifies code readability and navigation.
-
Since
publicmember variables (inside structs) have no trailing underscore (_), write constructors like this (i.e. use trailing underscores for the constructor function arguments instead):struct Point { double x; double y; Point(double x_, double y_) : x(x_), y(y_) { } };
-
Do not shadow variables declared in classes/structs. For example, do not name a local variable
xif you have a public member variablexin the same class.Avoid constructions such as
this->x = x;. Exception: when you need a nondependent name from a dependent base class (for a full explanation, see here). -
All virtual function overrides should be qualified with the
overridekeyword.This enables better compile-time checking whether a function is indeed meant to be overridden, and can prevent the classical bug of either the function name or the arguments being all so slightly different.
Virtual function overrides not qualified with the
overridekeyword will be considered erroneous. -
Always enforce const-correctness: Make functions that do not modify (visible) state
const, otherwise non-const. The virality of const-ness is a feature, not a bug.Code that is not const-correct will be considered erroneous.
constsignifies logical const-ness. In cases where only internal state is modified (e.g. locking a mutex), make the state-changing variablemutable. Ensure all functions markedconstwork correctly in the face of multithreading and being accessed from multiple threads.