Draft Version 1.0, February 23, 2015
This document is a draft of new C++ coding guidelines compiled for the STAR collaboration by the above mentioned authors. This effort was initiated by the STAR computing coordinator Jerome Lauret on October 31, 2014. The charge can be viewed here. The committee produced two documents, one for the coding guidelines seen here, and one for the naming and formatting guidelines that can be viewed here.
The committee based their work on the existing guidelines, expanded them for clarity, and added new material where it saw fit. The coding guidelines include the new C++11 standard. We have made heavy use of the C++ Google Style guide at http://google-styleguide.googlecode.com using their xml and css style sheets.
The goal of this guide is to manage the complexity of C++ (often in conjunction with ROOT) by describing in detail the dos and don'ts of writing C++ code. These rules exist to keep the STAR code base manageable while still allowing coders to use C++ language features productively. In some cases we constrain, or even ban, the use of certain C++ and ROOT features. We do this to keep code simple and to avoid the various common errors and problems that these features can cause. We also had to take into account that millions of lines of STAR code exist. For a new experiment the guidelines certainly would look different in places but we have to live with the legacy of existing code and the guidelines under which they were written.
Note that this guide is not a C++ tutorial: we assume that the reader is familiar with the language. We marked parts of the guidelines that address specifically new C++11 features.
Hooray! Now you know you can expand points to get more details. Alternatively, there are an "expand all summaries" and an "expand all summaries and extra details" at the top of this document.
In general, every .cxx
file should have an associated
.h
file. Each header file should contain only one or related class declarations for maintainability and for easier retrieval of class definitions.
Correct use of header files can make a huge difference to the readability, size and performance of your code. The following rules will guide you through the various pitfalls of using header files.
#define
guards to
prevent multiple inclusion. The format of the symbol name
should be
<FILE>_H
.
For example, the file
myFile.h
should
have the following guard:
#include
s.
#include
lines can often be replaced with forward declarations of whatever
symbols are actually used by the client code.
#include
s force the compiler to open
more files and process more input.std::
usually yields undefined behavior.#include
that header.#include
its
header file.#include
the appropriate header.#include
.#include
the file that actually provides the
declarations/definitions you need; do not rely on the symbol being
brought in transitively via headers not directly included. One
exception is that myFile.cxx
may rely on
#include
s and forward declarations from its corresponding
header file myFile.h
.
.cxx
file and let the compiler decide what gets inlined (it can decide anyway, regardless of the inline keyword).
Use inline when you require the implementation of a function in multiple translation units (e.g. template classes/functions).
The inline keyword indicates that inline substitution of the function body at the point of call is to be preferred to the usual function call mechanism. But a compiler is not required to perform this inline substitution at the point of call.
Functions that are defined within a class definition are implicitly inline. Note, however, that the definition of functions in the class definition is strongly discouraged in STAR.
An inline function must be defined in every translation unit from where it is called. It is undefined behavior if the definition of the inline function is not the same for all translation units. Note that this implies that the function is defined in a header file. This can have an impact on compile time and lead to longer (= less efficient) development cycles.
Note that the inline keyword has no effect on the linkage of a function. Linkage can be changed via unnamed namespaces or the static keyword.
If you add a new function, put it into the .cxx
file per default.
Small functions, like accessors and mutators may be placed into the .h
file instead (inline
).
Also, most template function implementations need to go into the .h
file.
If you later determine that a function should be moved from the .cxx
file into the .h
file, please make sure that it helps the compiler in optimizing the code.
Otherwise you're just increasing compile time.
There are two types of #include statements: #include <myFile.h>
and #include “myFile.hâ€
.
The header files of external libraries are obviously not in the same directory as your source files. So you need to use angle brackets.
Headers of your own application have a defined relative location to the source files of your application. Using double quotes, you have to specify the correct relative path to the include file.
Include orderAnother important aspect of include management is the include order. Typically, you have a class named Foo, a file Foo.h and a file Foo.cxx . The rule is : In your file Foo.cxx, you should include Foo.h as the first include, before the system includes.
The rationale behind that is to make your header standalone.
Let's imagine that your Foo.h looks like this:
And your Foo.cxx looks like this:
Your Foo.cxx file will compile, but it will not compile for other people using Foo.h without including Bar.h.
Including Foo.h first makes sure that your Foo.h header works for others.
For more details: Getting #includes right.
Namespaces subdivide the global scope into distinct, named
scopes, and thus are useful for logically grouping related types and functions and
preventing name collisions. In C++ it is in general very good practice to use namespaces,
especially in libraries. However, historically STAR software makes little to no use of namespaces
but rather uses a specific naming scheme (prefixes) to indicate the scope
(e.g. StEmc
..., StTpc
... etc). While certain tools in STAR can handle
namespaces (such as cons
) others would be very cumbersome to adapt.
StEvent
.
When using namespaces in end-user parts of your code (e.g. specific analysis code),
encapsulate your entire class into the namespace.
Nonmember functions that are logically tied to a specific type should be in the same namespace as that type.
To make namespace work with cons
and ROOT use the STAR specific $NMSPC tag as follows
std
, not even forward
declarations of standard library classes.
Declaring entities in namespace std
represents undefined behavior,
i.e., not portable. To declare entities from the standard library, include
the appropriate header file.
Putting nonmember functions in a namespace avoids polluting the global namespace. Static member functions are an alternative as long as it makes sense to include the function within the class.
Variables whose lifetime are longer than necessary have several drawbacks:
If the variable is an object, its constructor is invoked every time it enters scope and is created, and its destructor is invoked every time it goes out of scope.
It may be more efficient to declare such a variable used in a loop outside that loop:
Do not separate initialization from declaration, e.g.
Use a default initial value or ternary operator (?:) to reduce mixing data flow with control flow.
Prefer declaration of loop variables inside a loop, e.g.
In C++11, the brace initialization syntax for builtin arrays and POD structures has been extended for use with all other datatypes.
Example of brace initialization:
Example of single-argument assignments:
User data types can also define constructors that take
initializer_list
, which is automatically created from
braced-init-list:
Finally, brace initialization can also call ordinary constructors of
data types that do not have initializer_list
constructors.
Never assign a braced-init-list to an auto local variable. In the
single element case, what this means can be confusing.
For clarity of the examples above we use directly explicit values, however following
the rule about magic numbers requires to define all such
numbers as named constants or constexpr
first.
constexpr
constructor.
This is the case for fundamental types (integers, chars, floats, or pointers) and POD (Plain Old Data: structs/unions/arrays
of fundamental types or other POD structs/unions). main()
.
Destructors of globals can be used to run code after main()
.
Dynamic initialization of globals in dynamically loaded objects can be used to run code on plugin load.
As an example do this:
But not this :
std::vector
(use std::array
instead), or
std::string
(use const char []
) for global variables.
If there is a need for startup or shutdown code, dynamic constructors may be used. But only if:
Example that exhibits the problem of execution order:
This program will either output "Hello World" or crash, depending on the initialization order (with GCC on Linux it depends on whether you link with g++ Struct.o main.o
or g++ main.o Struct.o
).
Function-local static variables can be used to build factory functions for lazily initialized global objects. This makes it possible to safely use dynamically initialized types in the global scope.
In case function-local static variables are nevertheless used, it is best to avoid non-owning references
because their destruction happens after return from main()
, as shown on the following
code snippets :
Every class should have at least one constructor (and a destructor)
even if it does nothing or is defined to do nothing, or compiler defaults should be explicitly
requested using the default
specifier. This is good practice, it indicates to
the reader that the coder thought about this and not plainly forgot about it.
If a class does not have a constructor there is no guarantee of how objects of that class will be initialized, whilst data members should be explicitly initialized therein. When the constructor dynamically allocates memory, a destructor must be added to return the memory to the free pool when an object gets cleaned up.
In the constructor all data member should be initialized, either in the constructor function body or in the constructor initializer list or in-class. See the next item for more.Class member variables are initialized in the order in which they are declared in the class definition. The order in the constructor initializer list has no influence on initialization order and therefore may be misleading if it does not match the order of the declaration. Compilers often issue a warning if this rule is broken, but not always.
If a member variable is not explicitly initialized in the constructor initializer list and it is a non-POD the default constructor of that variable is called.
Therefore, if a member variable is assigned to in the constructor function body, the member variable may get initialized unnecessarily with the default constructor.
If the variable is a POD and the class instance is created with the new
operator the variable will be zero-initialized. Otherwise, PODs are left uninitialized
see examples below.
If you do not declare any constructors yourself then the compiler will generate a default constructor for you, which may leave some fields uninitialized or initialized to inappropriate values.
Examples:
Initialization list:
See an example of initialization via std::initializer_list
in Brace Initialization.
Non explicit initialization:
Member variables should be declared and initialized in the same order.
Use in-class member initialization for simple initializations, especially when a member variable must be initialized the same way in more than one constructor.
If your class defines member variables that aren't initialized in-class, and if it has no other constructors, you must define a default constructor (one that takes no arguments). It should preferably initialize the object in such a way that its internal state is consistent and valid.
If your class inherits from an existing class but you add no new member variables, you are not required to have a default constructor, also see Delegating and Inheriting Constructors.
Inside constructors and destructors virtual function do not behave "virtually". If the work calls virtual functions, these calls will not get dispatched to the subclass implementations. Calls to an unimplemented pure virtual function result in undefined behavior.
Calling a virtual function non-virtually is fine:
= delete
This is particularly useful in two cases:
1) Making objects non-copyable:
= default
can be used to state the programmers wish for defaults to be created.
= default
.
The assignment operator
It is possible to implement one using the other. One can safely invoke the copy assignment operator from the constructor as long as the operator is not declared virtual.
The rule of three is a rule of thumb in C++ that claims that if a class defines one of the following it should probably explicitly define all three:
Exception 1: do not implement your own copy/assignment when member-wise copy is desired and request the compiler to generate the defaults instead. For example, use:
Exception 2: if the design of the class asks specifically demands that no copy is created (e.g. for singletons) the copy constructor and assignment
operators should be be disabled by using the delete
keyword (C++11) or by making them private (C++98 or older).
Polymorphic class design implies pointer semantics. Since copy constructors/assignment operators, etc. cannot be made virtual, making a base class copyable could result in objects slicing.
If your polymorphic class needs to be copyable, consider using a virtual clone()
method.
This way copying can be implemented without slicing and be used more naturally for pointers:
1) If any move, copy, or destructor is explicitly specified (declared, defined, =default, or =delete) by the user, no move is generated by default.
2) If any move, copy, or destructor is explicitly specified (declared, defined, =default, or =delete) by the user, any undeclared copy operations are generated by default, but this is deprecated, so don't rely on that.
std::vector
does not need to copy all data, but only the pointer to the data.
Additionally, the source object must be told that it does not own the data anymore, to inhibit the free
from the destructor.
In most cases the move constructor/assignment operator therefore modifies the source object (e.g. setting the data pointer to nullptr
).
See rvalue reference and move semantics for more details on move semantics implementation.
Only implement move constructors/assignment operators if your class needs optimizations for move semantics.
Delegating and inheriting constructors are two different features, both introduced in C++11, for reducing code duplication in constructors. Delegating constructors allow the constructor to forward work to another constructor of the same class, using a special variant of the initialization list syntax.
Many classes have multiple constructors, especially in STAR. Often, an empty constructor sets variables to either 0 or any default parameters, while the non-empty constructors are used to set the data member to the values provided. Other examples are constructors that take different arguments, depending on the context the class is used. Often only parts of all data that defined in the class are known at construction time.
In many cases this requires to either write constructors with semi-identical code making
code maintenance difficult, or providing a private init()
function that is called
internally by the various constructors.
This revision eliminates code duplication but it brings the following new problems:
Other member functions might accidentally call init()
, which causes unexpected results.
After we enter a class member function, all the class members have already been constructed.
It's too late to call member functions to do the construction work of class members.
In other words, init()
merely reassigns new values to data members.
It doesn’t really initialize them.
Delegating constructors make the program clear and simple. Delegating and target constructors do not need special labels or disposals to be delegating or target constructors. They have the same interfaces as other constructors. A delegating constructor can be the target constructor of another delegating constructor, forming a delegating chain. Target constructors are chosen by overload resolution or template argument deduction. In the delegating process, delegating constructors get control back and do individual operations after their target constructors exit.
clang
submits an error, gcc
compiles and crashes with stack overflow.
A derived class, per default, inherits all functions of the base class. This is not the case for constructors. Since C++11 it is possible to explicitly inherit the constructors of a base class. This can be a significant simplification for derived classes that don't need custom constructor logic.
This is especially useful when Derived
's constructors
don't have to do anything more than calling Base
's
constructors.
Use delegating and inheriting constructors when they reduce code duplication.
Be cautious about inheriting constructors when your derived class has new member
variables and use in-class member initialization for the derived class's member variables.
When coding delegating constructors be aware of self delegation.
In polymorphic design a special care is needed in implementing
base class destructors. If deletion through a pointer to a base
Base
should be allowed, then the Base
destructor must be public and virtual. Otherwise, it should be
protected and can be non-virtual.
Always write a destructor for a base class, because the implicitly generated one is public and non-virtual.
struct
only for passive objects that carry data;
everything else is a class
.
The struct
and class
keywords behave
almost identically in C++. We add our own semantic meanings
to each keyword, so you should use the appropriate keyword for
the data-type you're defining.
structs
should be used for passive objects that carry
data, and may have associated constants, but lack any functionality
other than access/setting the data members. The
accessing/setting of fields is done by directly accessing the
fields rather than through method invocations. Methods should
not provide behavior but should only be used to set up the
data members, e.g., constructor, destructor,
initialize()
, reset()
,
validate()
.
If more functionality is required, a class
is more
appropriate.
You can use struct
instead of class
for functors and traits.
Note that member variables in structs and classes have different naming rules.
public
and declare overridden methods
as override
or final
.
However, composition is often more appropriate than inheritance especially if a
class is not designed to be a base class.
Since C++11 it is possible to mark virtual methods as overriding a virtual methods from the base class using the keyword override
.
This is useful to state the intent and get a compile error (on otherwise silent errors) if this intent is not fulfilled for some reason
(e.g. typo in the method name, mismatching method signature, virtual keyword forgotten in the base class).
For example:
The final
keyword tells the compiler that derived classes may not override the virtual methods anymore.
This is useful to limit abuse of your classes by users, but it closes the possibility of better implementation of methods in derived classes.
All inheritance should be public
. If you want to
do private inheritance, you should be including an instance of
the base class as a member instead.
Do not overuse implementation inheritance. Composition is often more appropriate.
State your intent when you want to override a virtual method by using the keyword override
.
A class is a pure interface if it meets the following requirements:
= 0
") methods
and static methods (see Destructors).
For example:
a << 1;
shifts the bits of the variable left by one bit if a is an integer, but if a is an output stream instead this will write "1" to it.
The semantics of the operator overloading should be kept the same. Because operator overloading allows the programmer to change the usual semantics of an operator, it should be used with care.
public
, protected
and private
keywords must be used explicitly
in the class declaration in order to make
code more readable. It is recommended to list the public data member and methods first
since they define the global interface and are most important for the user/reader.
Don't do this:
Do this:
Information hiding protects the code from uncontrollable modifying state of your object by clients and it also help to minimize dependencies between calling and called codes.
A class consisting mostly of gets/sets is probably poorly designed.
Consider providing an abstraction or changing it in struct
.
private
, except in structs
.
If there is no better way how to hide the class internals,
provide the access through protected or public accessor and,
if really needed, modifier functions.
See also Inheritance, Structs vs. Classes and Function Names.
ROOT defines a large set of portable and unportable types such as Int_t
,
Float_t
, Double_t
, and many more.
There is a priori no reason to use any of those when not needed. Use of builtin C++
types makes the code in fact more portable, readable, and often faster (see discussion under
"Extended integer types").
If fixed size is absolutely required the introduction of extended integer types of
fixed size and guaranteed size types in C++11 makes ROOT types redundant.
The only exceptions are
data member in “persistent†classes (e.g. StEvent
) under schema evolution. Here ROOT types
need to be used to define the data members.
int
instead Int_t
, float
instead of Float_t
,
double
instead of Double_t
, etc.
<cmath>
) over those
provided by ROOT.
ROOT provides a rich set of special mathematical functions often adapted from the old CERNLIB or,
more recently, wrapped GSL functions. They are heavily used in STAR code. However, ROOT
also provides a set of basic mathematical functions that are already defined in <cmath>
.
Examples are TMath::Sqrt()
, TMath::Log()
, TMath::Sin()
, and many more.
There is no rational reason to use these ROOT functions when the same functionality is available in the standard
and defined in <cmath>
. In most cases they are implemented by calling the built-in functions anyway
and their use reduces readability and portability.
<cmath<
is strongly discouraged.
Use sqrt()
instead of TMath::Sqrt()
, use log()
instead
of TMath::Log()
, use sin()
instead of TMath::Sin()
, etc.
Vendors use a multitude of methods to add specific information into source code, mostly through preprocessor/macro statements such as
__attribute__
, __declspec
, and #pragma
.
Attributes were added to C++ in order to unify and streamline this procedure.
As such their use for the common (STAR) programmer is limited. An attribute can be used almost everywhere in the C++ program,
and can be applied to almost everything: to types, to variables, to functions, to names, to code blocks, and to entire translation units,
although each particular attribute is only valid where it is permitted by the implementation.
An attribute is placed within double square brackets. There are few attributes defined yet: [[noreturn]]
,
[[carries_dependency]]
, [[deprecated]]
(C++14), and [[deprecated("reason")]]
(C++14).
Future attributes are in discussion to support MP.
Exceptions should be used for error handling.
Exception classes typically derive from std::exception
(or one of its subclasses like std::runtime_error
).
Exceptions should be scoped inside the class that throws them.
By default, catch exceptions by reference.
const
.
Design const-correct interfaces.
Consider
constexpr
for some uses of const.
The Standard Library […] in simple words says that it expects operations on const objects to be thread-safe. This means that the Standard Library won't introduce a data race as long as operations on const objects of your own types either
This is a great example of how C++11 is a simpler language: we can stop the Cold War-era waffling about subtleties about what 20th-century C++ const means, and proudly declare modern C++ const has the simple and natural and “obvious†meaning that most people expected all along anyway.
[…] Bjarne Stroustrup writes: “I do point out that const means immutable and absence of race conditions in the last Tour chapter. […]â€
[Source: isocpp.org]const
to indicate that the variables are logically immutable.
(Because of const_cast
and mutable
member variables, and global variables, const
is no hard guarantee for immutability.)
Member functions can be declared const
to allow calls with const
this
pointer.
Note that overloading member functions on const
is possible.
const
variables, data members, methods and
arguments add a level of compile-time type checking; it
is better to detect errors as soon as possible.
Therefore we strongly recommend that you use
const
whenever it makes sense to do so.
Use const
:
const
methods;const
pointer or non-const
reference to a data member.
mutable
can be used to make objects that are already threadsafe (such as std::mutex
) mutable in const
methods.
Thus, it is possible to make const
methods thread-safe, through internal synchronization.
constexpr
to define true constants or to ensure constant initialization.
Another benefit of constexpr
, beyond the performance of compile time computation, is that it
allows functions to be used in all sorts of situations that previously would have called for macros.
For example, let's say you want to have a function that computes the the size of an array based on
some multiplier. If you had wanted to do this in C++ without a constexpr
, you'd have needed to create
a macro since you can't use the result of a function call to declare an array. But with constexpr
,
you can now use a call to a constexpr
function inside an array declaration.
Note that a constexpr
specifier used in an object declaration implies
const
. A constexpr
specifier used in a function
declaration implies inline. If you declare a class member function to be constexpr
,
that marks the function as const
as well. If you
declare a variable as constexpr
, that in turn marks the variable as
const
. However, it doesn't work the other way--a
const
function is not a constexpr
,
nor is a const
variable a constexpr
.
You can make any object a constexpr
. In this case the constructor must be
declared a constexpr
as well as the method to be used.
constexpr
functionsconstexpr
global variables
Some variables can be declared constexpr
to indicate the variables are true constants,
i.e. fixed at compilation/link time.
Some functions and constructors can be declared constexpr
which enables them to be used
in defining a constexpr
variable.
constexpr
enables
definition of constants with floating-point expressions
rather than just literals;
definition of constants of user-defined types; and
definition of constants with function calls.
constexpr
may cause migration problems if later on it has to be downgraded.
Current restrictions on what is allowed
in constexpr
functions and constructors
may invite obscure workarounds in these definitions.
constexpr
definitions enable a more robust
specification of the constant parts of an interface.
Use constexpr
to specify true constants
and the functions that support their definitions.
Avoid complexifying function definitions to enable
their use with constexpr
.
Do not use constexpr
to force inlining.
constexpr
variables are constant expressions, they can still have an address.
Thus, using a constexpr
variable as argument for a const-ref function parameter requires the constexpr
variable to have a symbol.
Consider the following header file:
.cxx
file.
auto
.
It is useful mostly in templates and in methods where the return type is the class itself. The new return syntax,
however, is not as easy to read as the standard method and should only be used where necessary. It should not be
regarded as an alternative way of defining a simple function.
In all prior versions of C and C++, the return value of a function absolutely had to go before the function:
auto
for the name of the return type.
In the above example the use of the new syntax does not provide any advantage, in fact it makes it less readable. However, there are several cases were the new syntax is in fact the only way to make things work.
Consider:x+y
", of course,
but how can we say that? First idea, use decltype
:
decltype
. The new return syntax, however, is not as easy to read as the
standard method and should only be used where necessary and to simplify the code. It should not be
regarded as an alternative way of defining a simple function. Use the suffix syntax only if absolutely required.
unique_ptr<T>
,
shared_ptr<T>
, and weak_ptr<T>
.
Also, the standard library provides make_shared<T>
and starting with C++14 also make_unique<T>
.
Unique ownership ensures that there can be only one smart pointer to the object. If that smart pointer goes out of scope it will free the pointer.
Shared ownership allows to have multiple pointers to an object without deciding who is the exclusive owner.
Thus the owners can be freed in any order and the pointer will stay valid until the last one is freed, in which case the pointer is also freed.
Note that shared_ptr<T>
is thread-safe and thus enables sharing ownership over multiple threads.
Example:
second
is freed automatically, because the last
reference to it went out of scope. But even though third
went out of scope here,
no free occurred because first
still has a reference. Only when first
went out of scope and as it is the last reference, third
is automatically freed.
StMaker
s).
Avoid magic numbers (hard coded numbers).
Avoid spelling literal constants like 42
or
3.141592
in code. Hard-coded numbers within the code reduce portability and make maintainability
harder. The use of symbolic names and expressions (declared ) is a valid solution. Make use of const
and constexpr
. Names add information and introduce
a single point of maintenance.
Example of constants at namespace level:
Example of class-specific constants:
For values which are likely to change with time, a database approach should be considered. Refer to the STAR database Web page area for more information.
Macros mean that the code you see is not the same as the code the compiler sees. This can introduce unexpected behavior, especially since macros have global scope.
The following usage pattern will avoid many problems with macros; if you use macros, follow it whenever possible:
.h
file.
#define
) right before you use them,
and undefine them (via #undef
) right after.
#undef
) before
replacing it with your own; instead, pick a name that's
likely to be unique.
##
to generate function/class/variable
names.
Long functions are hard to debug and makes readability difficult. Short functions allow code reuse.
If a function exceeds about 40 lines, think about whether it can be broken
up without harming the structure of the program.
Giving the function a name that describes what it does might help splitting it into smaller pieces.
Functions should represent logical grouping, therefore it should be easy to assign them meaningful names.
Please note that nesting is not the same as splitting long functions into short ones.
In addition, it does not improve readability and ease of debug.
dynamic_cast
consider reviewing the design of your code and classes.
typeid
or
dynamic_cast
.
Querying the type of an object at run-time frequently means a design problem and is often an indication that the design of your class hierarchy is flawed.
Undisciplined use of RTTI makes code hard to maintain. It can lead to type-based decision trees or switch statements scattered throughout the code, all of which must be examined when making further changes.
Decision trees based on type are a strong indication that your
code is on the wrong track.
The standard alternatives to RTTI (described below) require modification or redesign of the class hierarchy in question. Sometimes such modifications are infeasible or undesirable, particularly in widely-used or mature code.
RTTI can be useful in some unit tests. For example, it is useful in tests of factory classes where the test has to verify that a newly created object has the expected dynamic type. It is also useful in managing the relationship between objects and their mocks.
RTTI has legitimate uses but is prone to abuse, so you must be careful when using it. You may use it freely in unit tests, but avoid it when possible in other code. In particular, think twice before using RTTI in new code. If you find yourself needing to write code that behaves differently based on the class of an object, consider one of the following alternatives to querying the type:
When the logic of a program guarantees that a given instance
of a base class is in fact an instance of a particular derived class,
then use of a dynamic_cast
or static_cast
as an alternative may be also justified in such situations.
An example of code based on dynamic_cast
:
which can be defined using the Visitor pattern:
static_cast
when necessary, but avoid const_cast
and reinterpret_cast
.
C-casts are forbidden.
static_cast
to explicitly convert values between different types.
static_cast
s are useful for up-casting pointers in an inheritance hierarchy.
const_cast
.
(Possibly use mutable
member variables instead.)
const_cast
may be used to adapt to const-incorrect interfaces that you cannot (get) fix(ed).
reinterpret_cast
s are powerful but dangerous.
Rather try to avoid them.
Code that requires a reinterpret_cast
should document the aliasing implications.
(reinterpret_cast on cppreference.com)
See the RTTI section
for guidance on the use of dynamic_cast
.
reinterpret_cast
consider:
reinterpret_cast
in order to avoid mistakes.
alloca()
.
alloca
allow variably-sized stack allocations, whereas all other stack allocations in C++ only allow fixed-size objects on the stack.
alloca
is part of POSIX, but not part of Standard C++.
++i
) and decrement
(--i
) operators because it has simpler semantics.
If not conditional on an enumerated value, switch statements
should always have a default
case.
Empty loop bodies should use {}
or continue
.
If not conditional on an enumerated value, switch statements
should always have a default
case (in the case of
an enumerated value, the compiler will warn you if any values
are not handled). If the default case should never execute,
simply
assert
:
Empty loop bodies should use {}
or
continue
, but not a single semicolon.
const
) in range-for statements especially when
dealing with large objects. Prefer ordinary loops when you need the index information.
begin()
and end()
are defined to return iterators.
Programmers often need both, the elements of an iterable collection and its index. This is not directly supported in C++11.
Unless optimized away by compiler, using a copy of element could come at a performance cost if the element type is large.
Range-for loops are useful and should be used.
Ordinary loops should be preferred when programmer needs the element index. Avoid having your own counter.
Using reference to elements is encouraged when dealing with large objects. Use const
reference when you don't need to modify the object state.
int
if you need an integer type. If you need to guarantee a specific size use the new extended integer
types defined in <cstdint>
.
There are five standard signed integer types in C++:
Already implemented by many compilers the C++11 standard makes these types now official.
Signed integer type with width of exactly 8, 16, 32 and 64 bits respectively with no padding bits and using 2's complement for negative values (provided only if the implementation directly supports the type).u
.
While these types are sufficient for most tasks, there are times where the precise size has to be defined.
In this case consider one of the integer types in <cstdint>
.
Already before C++11 these types were implemented in most compilers but C++11 makes it official. Three types are defined in header
<cstdint>
: Exact-Width Types, Minimum-Width Types, and Fastest Minimum-Width Types.
This set identify types with precise sizes. The general form is int
N_t
for signed types
and uint
N_t
for unsigned types,
with N indicating the number of bits. Note, however, that not all systems can support all the types. For example, there
could be a system for which the smallest usable memory size is 16 bits; such a system would not support the
int8_t
and uint8_t
types.
The minimum-width types guarantee a type that is at least a certain number of bits in size. These types always exist.
For example, a system that does not support 8-bit units could define int_least_8_t
as a 16-bit type.
For a particular system, some integer representations can be faster than others. For example, int_least16_t
might be
implemented as short
, but the system might do arithmetic faster using type int
. So <cstdint>
also defines the fastest
type for representing at least a certain number of bits. These types always exist. In some cases, there might be
no clear-cut choice for fastest; in that case, the system simply specifies one of the choices.
Below we give a list (incomplete) of possible portability issues:
printf()
specifiers for some types are
not cleanly portable between 32-bit and 64-bit
systems. sizeof(void *)
!=
sizeof(int)
. Use intptr_t
if
you need a pointer-sized integer.
0
for integers, nullptr
for pointers,
and '\0'
for chars.
nullptr
is a pointer literal of type std::nullptr_t
. On the other hand, NULL
is a macro equivalent the integer 0
. Using NULL
could bring to unexpected problems. For example imagine you have the following two function declarations:
NULL
is 0
, and 0
is an integer,
the first version of function
will be called instead.
In C++11, nullptr
is a new keyword that can (and should!) be used to represent NULL
pointers.
sizeof(varname)
to
sizeof(type)
.
Use sizeof(varname)
when you take the size of a particular variable.
sizeof(varname)
will update
appropriately if someone changes the variable type
either now or later.
You may use sizeof(type)
for code unrelated to any particular variable,
such as code that manages an external or internal
data format where a variable of an appropriate C++ type
is not convenient.
auto
keyword.
Use auto
to avoid type names that are just clutter.
Continue to use manifest type declarations when it helps readability,
and never use auto
for anything but local variables.
Use auto
against verbosity, not consistency. In cases where the rhs
expression is an integer or floating point literal the use of auto is strongly discouraged.
auto
will be given
a type that matches that of the expression used to initialize
it. You can use auto
either to initialize a
variable by copying, or to bind a reference.
C++ type names can sometimes be long and cumbersome,
especially when they involve templates or namespaces. In a statement like
Without auto
we are sometimes forced to write a
type name twice in the same expression, adding no value
for the reader, as in
Using auto
makes it easier to use intermediate
variables when appropriate, by reducing the burden of writing
their types explicitly.
Sometimes code is clearer when types are manifest, especially when
the initialization of a variable depends on functions/variables that were declared
far away. In an expression like
i
's type is, if x
was declared hundreds of lines earlier.
Programmers have to understand the difference between auto
and const auto&
or they'll get copies when
they didn't mean to.
The interaction between auto
and C++11
brace-initialization can be confusing. The declarations
xValue
is
an int
, while yValue
is
an initializer_list
. The same applies to other
normally-invisible proxy types.
If an auto
variable is used as part of an
interface, e.g. as a constant in a header, then a programmer
might change its type while only intending to change its
value, leading to a more radical API change than intended.
There is a certain danger using auto
with numerical literals.
auto
is permitted for local variables only.
Do not use auto
for file-scope or namespace-scope
variables, or for class members. Do not use auto
for numeric literals.
Never assign a braced initializer list to an auto
-typed variable.
begin()
and end()
functions are a new addition to the standard library,
promoting uniformity, consistency and enabling more generic programming. They work with
all STL containers, but more than that they are overloadable, so they can be extended to
work with any type. Overloads for C-like arrays are also provided.
The use of non-member begin()
and end()
is encouraged.
begin()
and end()
methods that return an iterator
to the beginning and the end of the container. Therefor iterating over a container could look like this:
begin()
and end()
, which makes them
impossible to use with the STL algorithms or any other user-defined template function that requires
iterators. That is even more problematic when using C arrays.
The non-member begin()
and end()
methods are extensible, in the sense they can be overloaded for
any type (including C arrays).
*
,
prefix increment (++itr
) and !=
.
begin()
and end()
allows one to write very generic methods and is hence encouraged.
static_assert()
performs an assertion check at compile-time.
Type traits and static_assert
is mostly for
template class developer . Since the use of templates in STAR is minimal, these new C++11
features will be rarely used, if at all. There’s no argument against using this feature
if needed.
static_assert()
performs an assertion check at compile-time. If the assertion is true, nothing happens. If the assertion is false, the compiler displays the specified error message.
static_assert()
becomes more useful when used together with type traits. These are a
series of classes that provide information about types at compile time. They are available
in the <type_traits>
header. There are several categories of classes in this header:
helper classes, for creating compile-time constants, type traits classes, to get
type information at compile time, and type transformation classes, for getting new
types by applying transformation on existing types.
static_assert()
does not violate STAR’s messaging scheme since the assert error messages are printed
at compile not run time.
If you can take its address using the built-in address-of operator (&) then it is an lvalue, otherwise, it is an rvalue.
Another useful rule of thumb that is useful but not strictly correct is the if-it-has-a-name rule: if it has a name then it is an lvalue, otherwise, it is an rvalue.
Rvalue reference is designated with an && as opposed to & for lvalue reference.
Here is an example of function overloading to handle lvalue and rvalue arguments separately:
The move semantics allow to get rid of expensive copies from temporary (rvalue) objects when a move is intended. Now that we can detect temporary objects using rvalue references we can overload the copy/assignment functions to do the less expensive move from the temporary object by simply pointing the current object's pointers to the temporary object's resources and nullifying the latter's pointers. To add to the multitude of examples of move semantics implementation here is one:
x
an lvalue reference, same if x
is an rvalue. Now when does this matter? It doesn't matter inside print
itself
since x
is an lvalue there anyway. It matters when you want to pass x
to another function, do you pass it as an lvalue (just x
) or hide its
name using std::move
? The answer obviously depends on the nature of x
, you want to preserve that. This can be achieved using std::forward
. std:forward passes
rvalue references as rvalues and lvalue references as lvalues.
Strive to define your move semantics so that they cannot throw exceptions and declare them so using noexcept
specifier.
Use std::move
to pass argument to base classes in move constructor and assignment operator.
Use std:forward
to forward arguments to classes constructors in templated functions or classes.
Remember that an rvalue reference is not necessarily an rvalue itself.
Take advantage of compilers Return Value Optimization (RVO)/elision, don't be afraid to return by value.
The coding conventions described above have to be followed. However, like all good rules, these sometimes have exceptions.
To modify code that was written to specifications other than those presented by this guide, it may be necessary to deviate from these rules in order to stay consistent with the local conventions in that code. In case of doubt the original author or the person currently responsible for the code should be consulted. Remember that consistency also includes local consistency.
Use common sense and BE CONSISTENT.
The point about having style guidelines is to have a common vocabulary of coding so people can concentrate on what the programmer is saying, rather than on how he/she is saying it.
OK, enough writing about writing code; the code itself is much more interesting. Have fun!
[1] Herb Sutter on software, hardware, and concurrency blog [http://herbsutter.com/2013/05/09/gotw-1-solution]