Each style point has a summary for which additional information is available by toggling the accompanying arrow button that looks this way: ▶. You may toggle all summaries with the big arrow button: ▶ Toggle all summaries About This Document This document attemps to define the guidelines for use of new C++11 features relevant to Geant4 code development. They were compiled by - Ivana Hřivnáčová; Institut de Physique Nucleaire (IPNO), Universite Paris-Sud, CNRS- IN2P3, Orsay, France
on behalf of Geant4 C++11 Task Force group
from the following sources: - Effective Modern C++ by Scot Meyers (O'Reilly). Copyright 2015 Scot Meyers. 978-1-491-90399-9.
- ALICE O² C++ Style Guide.
- cplusplus.com
- Stack Overflow
- It is using style sheets of C++ Google Style guide, Revision 3.274 (link) under the CC-By 3.0 License further modified by ALICE O² project.
- See also tutorial slides on C++11 guidelines.
To help understanding the examples, the following colour highlighting is applied: - The recommended code, using C++11 features, is presented in black colour on light green background (or in a green frame if PDF).
// code using C++11 features - The code using C++98 features, which can be improved with use of C++11 features, is presented in blue colour on light green background ((or in a green frame if PDF).
// code using C++98 features // which can be improved with use of C++11 features - The code, which is discouraged as it can be improved with C++11 features, is presented in red colour on light red background (or in a red frame if PDF).
// code which is error-prone or wrong // and which can be avoided using C++11 features
Deducing Types and auto link ▶ Prefer auto to explicit type declarations. In C++11, a variable whose type is given as auto will be given a type that matches that of the expression used to initialize it. - Example:
vector names; ... auto name1 = names[0]; // Makes a copy of names[0]. const auto& name2 = names[0]; // name2 is a reference to names[0].
Why to prefer auto: -
auto variables must be always initialized. int x1; // this code compiles but x1 may be used further // without being initialized auto x2; // this code will produce compilation error -
auto variables are generally immune to type mismatches that can lead to a portability or efficiency problems. In the code: std::unordered_map<:string int=""> m; for (const std::pair<:string int="">& p : m) { ... // do someting with p } the p type in the loop does not match the map m element type, which is std::pair<const std::string, int> (note the const ). The compiler will have to perform a conversion causing a creation of a temporary object. Such unintentional type mismatch is avoided with auto : std::unordered_map<:string int=""> m; for (const auto& p : m) { ... // do someting with p } - And obviously they typically require less typing and can ease the process of refactoring.
link ▶ auto -typed variables can be subject of pitfalls. Some of auto 's deduction type results, while conforming to the prescribed algoritms, may be different from the programmer expectations. - The interaction between
auto and C++11 brace-initialization can be confusing. The declarations auto x1 = 27; // type is int, value is 27 auto x2(27); // the same as above auto x3 = { 27 }; // type is std::initializer_list // value is {27} auto x4{ 27 }; // the same as above mean different things.
-
The same applies to other normally-invisible proxy types. The explicitly typed initializer idiom can be used to force auto to deduce the type you want it to have. For example, given a Matrix class which sum is implemented using a proxy class, such as Sum<Matrix, Matrix> : Matrix m1, m2, m3, m4; auto sum = m1 + m2 + m3 + m4; // the sum type will be different // from Matrix The sum type will be Sum<Sum<Sum<Sum<Matrix, Matrix>, Matrix>, Matrix> . The code with explicitly typed initializer idiom will look like this: Matrix m1, m2, m3, m4; auto sum = static_cast(m1 + m2 + m3 + m4); This idiom can be also used to make explicitly visible an intentionnal type conversion. For example the implicit conversion double calcEpsilon(); float ep = calcEpsilon(); // implicitly convert double to float is explicitly manifested in the code: double calcEpsilon(); auto ep = static_cast(calcEpsilon());
Modern Coding Style link ▶ Distinguish between () and {} when creating objects. In C++11, the braced initialization syntax for built-in arrays has been extended for use with all other datatypes. Example of braced initialization: std::vector<:string> myVector{"alpha", "beta", "gamma"}; Why to prefer braced initialization: In conctructor calls, parentheses and braces have the same meaning as long as std_initializer_list parameters are not involved . Otherwise the parameters are matched to std_initializer_list parameters if at all possible, even if other constructors offer seemingly better matches. -
Example of a class without std_initializer_list : class Widget { public: Widget(int i, bool b); Widget(int i, double d); };
Widget w1(10, true); // calls first ctor Widget w2{10, true}; // also calls first ctor Widget w3(10, 5.0); // calls second ctor Widget w2{10, 5.0}; // also calls second ctor -
Example of a class with std::initializer_list : class Widget { public: Widget(int i, bool b); // as before Widget(int i, double d); // as before Widget(std::initializer_list il); // added };
Widget w1(10, true); // calls first ctor as before Widget w2{10, true}; // now calls std::initializer_list // (10 and true convert to long double) Widget w3(10, 5.0); // calls second ctor as before Widget w2{10, 5.0}; // now calls std::initializer_list // (10 and 5.0 convert to long double) -
The choice between parenthesis and braces makes a significant difference when creating a std::vector of a numeric type with two arguments. std::vector v1(10, 20); // creates 10-element std::vector, // all elements have value of 20
std::vector v1{10, 20}; // creates 2-element std::vector, // element values are of 10 and 20
Never assign a braced-init-list to an auto local variable. In the single element case, what this means can be confusing. -
For example: auto value = {1.23}; // value is an initializer_list auto value = double{1.23}; // Good -- value is a double, // not an initializer_list.
link ▶ Prefer range-based for loop when iterating over all elements in a container or a braced initilizer list. Range-based for loop is a C++11 more readable equivalent to the traditional for loop operating over a range of values. -
Example: std::vector v = {0, 1, 2, 3, 4, 5};
for (auto i : v) { // access by value, the type of i is int std::cout }
for (const auto& i : v) { // access by const reference std::cout } -
It is safe, and in fact, preferable in generic code, to use deduction to forwarding reference auto&& : std::vector v = {0, 1, 2, 3, 4, 5};
for (auto&& i : v) { // access by reference, the type of i is int& std::cout } -
Example with a braced-init-list: auto&& : for(int n : { 0, 1, 2, 3, 4, 5 }) { // the initializer may be // a braced-init-list std::cout }
Consider use of for_each algorithm when you need to select particular begin and end values instead of looping over the whole container. See more in Algorithms. link ▶ Prefer nullptr to 0 and NULL . Using nullptr instead of 0 and NULL avoids overload resolution surprises, because nullptr can't be viewed as an integral type. link ▶ Prefer alias declarations to typedef s. typedef s do not support templatizations, but alias declarations do. The syntax with alias declarations is easier to understand in some constructs (e.g. function pointers). -
Example: // C++98 typedef std::unique_ptr<:unordered_map std::string="">> UPtrMapSS; // C++11 using UPtrMapSS = std::unique_ptr<:unordered_map std::string="">>;
link ▶ Use constexpr whenever possible. Some variables can be declared constexpr to indicate the variables are true constants, i.e. they are initialized with values known during compilation. -
Example of using constexpr variable: constexpr auto arraySize = 10; // 10 is a compile-time const
std::array data; // arraySize, as it is constexpr, // can be used to define array data In the following example, the arraySize2 cannot be set the values of non-constant variable sz , as its value is not known at run time: int sz; // non-constexpr variable ... constexpr auto arraySize2 = sz; // error ! sz's value not // known at compilation The array data also cannot be defined with the values of non-constant variable sz : std::array data; // error ! Note that const cannot be used in the same way because const objects need not to be initialized with values during compilation: int sz; // non-constexpr variable ... const auto arraySize3 = sz; // ok ! arraySize3 is // const copy of sz
std::array data; // error ! arraySize3 value not // known at compilation
Some functions and constructors can be declared constexpr which enables them to be used in defining a constexpr variable. constexpr objects and functions may be used in a wider range of contexts than non-constexpr objects and functions. constexpr is part of an object's or function's interface. Removing constexpr may cause large amounts of client code to stop compiling. link ▶ Prefer scoped enum s to unscoped enum s. Why to prefer scoped enum s: -
C++98 enum s are known as unscoped enum s. The names of the enumerators belong to the same scope as the enum . C++11 enum s declared via enum class don't leak names in this way. // C++98 enum Color98 { black, white, red };
Color98 c = white; // ok ! enumerator "white" is // in this scope // C++11 enum class Color11 { black, white, red }; Color11 c = white; // error ! no enumerator "white" is // in this scope Color11 c = Color11::white; // ok -
Scoped enum s prevent from implicit type conversions, they convert to other types only with a cast. -
Scoped enum s may always be forward-declared. Their default underlying type is int . Unscoped enum s have no default underlying type. They may be forward-declared only if declaration specifies an underlying type. enum class Color11; // ok enum Color98: int; // ok: underlying type is int
link ▶ Prefer deleted functions to private undefined ones. Usual practice with C++98, declaring copy constructor and assignment operator private prevents from clients calling them when copying the objects is not desired. In C++11, a better way to achieve essentially the same is the use of delete keyword. -
Example: // C++98 class Widget { ... private: Widget(const Widget&); // not defined Widget& operator=(const Widget&); // not defined }; // C++11 class Widget { public: Widget(const Widget&) = delete; Widget& operator=(const Widget&) = delete; };
Any function may be deleted, including non-member functions and template instantiations. They can be used to invalidate some undesired types in overloading. link ▶ Declare overriding functions override or final . When overriding functions in a derived class are derived with the override keyword the compiler will issue an error if a function with identical specification (including keyword virtual, the function name, parameters types, constantness, return types, exception specifications and reference qualifiers (new in C++11) is not declared in the base class. -
Example: class Base { public: virtual void mf1() const; virtual void mf2(int x); virtual void mf3() &; void mf4() const; } The following, C++98 code will compile with warnings (if they are activated) but will produce wrong results: // C++98 class Derived : public Base { public: virtual void mf1(); // ! missing const virtual void mf2(unsigned int x); // ! different argument type virtual void mf3() &&; // ! lvalue-qualified in Base, // rvalue-quelified in Derived virtual void mf4() const; // ! not virtaul in base class }; The following, C++11 code will generate compiler errors: // C++11 class Derived : public Base { public: virtual void mf1() override; // error ! virtual void mf2(unsigned int x) override; // error ! virtual void mf3() && override; // error ! virtual void mf4() const override; // error ! }; The final keyword tells the compiler that subclasses may not override the virtual function anymore. This is a special case, but useful to limit abuse of your classes by users.
link ▶ Declare constructors with one argument explicit , except for copy constructors and constructors with std::initializer_list . Normally, if a constructor takes one argument, it can be used as a conversion. For instance, if you define class Widget { public: Widget(const std::string &name); }; and then pass a string to a function that expects a Foo , the constructor will be called to convert the string into a Foo and will pass the Foo to your function for you. Declaring a constructor explicit prevents an implicit conversion. class Widget { public: explicit Widget(int number); // allocate number bytes explicit Widget(const std::string &name); // initialize with string name }; Constructors that take only an std::initializer_list may be non-explicit. This is to permit construction of your type using the assignment form for brace initializer lists (i.e. Widget myWidget = {1, 2} ). link ▶ Use delegating and inheriting constructors when they reduce code duplication. 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. For example: class Widget { public: Widget::Widget(const string& name) : mName(name) { } Widget::Widget() : Widget("example") { } ... }; A subclass, 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 subclasses that don't need custom constructor logic. class Base { public: Base(); explicit Base(int number); explicit Base(const string& name); ... };
class Derived : public Base { public: using Base::Base; // Base's constructors are redeclared here. }; This is especially useful when Derived 's constructors don't have to do anything more than calling Base 's constructors. 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. link ▶ Understand special member functions generation. The special member functions are those compilers may generate on their own: default constructor, destructor, copy operations and move operations (the last are C++11 only). Move operations are generated only for classes lacking explicitely declared moved operations, copy operations, and a destructor. The copy constructor is generated only for classes lacking an explicitely declared copy constructor, and it's deleted if a move operation is declared. The copy assignment operator is generated only for classes lacking an explicitely declared copy assignment operator, and it's deleted if a move operation is declared. Generation of the copy operation in classes with an explicitly declared destructor is deprecated. Member function templates never supress generation of special member function. The behavior of a class which relies on generating all special member functions can be accidentally changed by adding one of these functions, e.g. a destructor for logging functionality. This will cause that only copy operations are generated and then performed instead of moving operation what can make them significantly slower. Smart Pointers Smart pointers are objects that act like pointers, but automate ownership. There are two main semantics for ownership: unique and shared ownership. 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 pointee. Shared ownership allows to have multiple pointers to an object without deciding who is the exclusive owner. Thus the owners can be free'd in any order and the pointer will stay valid until the last one is freed, in which case the pointee is also freed. Smart pointers are extremely useful for preventing memory leaks, and are essential for writing exception-safe code. They also formalize and document the ownership of dynamically allocated memory. link ▶ Use std::unique_ptr for exclusive-ownership resource management. std::unique_ptr is a small, fast, move-only smart pointer for managing resource with exclusive-ownership semantics. By default, resource destruction take place via delete , but custom deleters can be specified. Stateful deleters and function pointers as deleters increase the size of std::unique_ptr objects. - Example:
{ std::unique_ptr uptr(new int(42));
std::cout std::cout } // uptr is automatically freed here
Converting std::unique_ptr to std::shared_ptr is easy. - Example:
{ std::unique_ptr uptr(new int(42)); std::shared_ptr sptr(std::move(uptr));
std::cout } Note that after the move the unique pointer does not point to the int object anymore.
link ▶ Use std::shared_ptr for shared-ownership resource management. std::shared_ptr offer convenience approaching that of garbage collection for the shared lifetime management of arbitrary resources. Compared to std::unique_ptr , std::shared_ptr objects are typically twice as big, incur overhead for control blocks, and require atomic reference count manipulations. Default resource destruction is via delete , but custom deleters are supported. The type of deleter has no effect on the type of std::shared_ptr . - Example:
{ std::shared_ptr sh1(new int); std::cout std::shared_ptr sh2(sh1); std::cout std::cout }
Avoid creating std::shared_ptr from variables of raw pointer type. - Example:
{ auto pw = new Widget; std::shared_ptr spw1(pw); // create control // block for *pw std::shared_ptr spw2(pw); // create 2nd control // block for *pw } Two control blocks for the same object, *pw , are created, and so also reference counts, each of which will eventually become zero, that will lead to an attempt to destroy *pw twice. This is fixed in the code below: { std::shared_ptr spw1(new Widget); // direct se of new std::shared_ptr spw2(spw1); // spw2 uses same // control block as spw1 }
link ▶ Use std::weak_ptr for std::shared_ptr -like pointers that can dangle. Potential use cases for std::weak_ptr include caching, observer lists, and the prevention of std::shared_ptr cycles. link ▶ Prefer std::make_unique (*) for std::make_shared to direct use of new. Compared to direct use of new , make functions eliminate source code duplication, improve exception safety, and , for std::make_shared and std::allocate_shared , generate code that's smaller and faster. - Example:
auto upw1(std::make_unique()); // with make function std::unique_ptr upw2(new Widget>); // without make function auto spw1(std::make_shared()); // with make function std::shared_ptr spw2(new Widget>); // without make function
Situations where use of make functions is inappropriate include custom deleters and a desire to pass braced initializers. For std::shared_ptr , additional situations where make functions may be ill-adviced include: - classes with custom memory management and
- systems with memory concerns, very large objects and
std::weak_ptr s that outlive the corresponding std::shared_ptr objects.
(*) std::make_unique is only part of C++14. Its implementation can be however easily added in C++11 based code. Both a simple version and a full-featured one are availble here . SL and Lambda Expressions link ▶ Understand lambda expressions. The C++ concept of a lambda function originates in the lambda calculus and functional programming. A lambda is an unnamed function that is useful (in actual programming, not theory) for short snippets of code that are impossible to reuse and are not worth naming. In C++ a lambda function is defined like this []() { } // barebone lambda or in all its glory []() mutable -> T { } // T is the return type, still lacking throw() [] is the capture list, () the argument list and {} the function body. The capture list defines what from the outside of the lambda should be available inside the function body and how. It can be either: - a value:
[x] - a reference
[&x] - any variable currently in scope by reference
[&] - same as 3, but by value
[=]
You can mix any of the above in a comma separated list [x, &y] . The argument list is the same as in any other C++ function. The function body contains the code that will be executed when the lambda is actually called. Return type deduction: If a lambda has only one return statement, the return type can be omitted and has the implicit type of decltype(return_statement) . An example for understanding lambda syntax: // use lamda as a function auto func = [] () { std::cout func(); // now call the function
// use lambda as an expression int i = ([](int j) { return 5 + j; })(6); std::cout See the next item for more meaningful use of lambda expression. link ▶ Prefer algorithm calls to handwritten loops. C++ includes useful generic functions like std::for_each , std::find_if or std::transform , which can be very handy. With C++98, they can also be quite cumbersome to use, particularly if the functor you would like to apply is unique to the particular function. // C++98
#include #include
namespace { struct f { void operator()(int) { // do something } }; }
void func(std::vector& v) { f f; std::for_each(v.begin(), v.end(), f); } If you only use f once and in that specific place it seems overkill to be writing a whole class just to do something trivial and one off. C++11 lambdas allow you to write an inline, anonymous functor to replace the struct f . For small simple examples this can be cleaner to read (it keeps everything in one place) and potentially simpler to maintain, for example in the simplest form: // C++11
void func2(std::vector& v) { std::for_each(v.begin(), v.end(), [](int) { /* do something here*/ }); } link ▶ Avoid default capture modes in lambda expressions. There are two default capture modes in C++11: by-reference and by-value. A lambda with the default by-reference (or by-value) capture mode can take by reference (or by value) any variable that is currently in scope: [&]() { /* do something here*/ } // default by-reference capture [=]() { /* do something here*/ } // default by-value capture Default by-reference capture can lead to dangling references. A problem can arise if the variable's life time is shorter than the life-time of the lambda and lambda can be then used with a dangling reference. Default by-value capture can lead to dangling pointers (especially this ). link ▶ Since C++11 the standard library provides unordered containers in headers <unordered_set> , <unordered_multiset> , <unordered_map> and <unordered_multimap> . Unordered maps are associative containers that store elements formed by the combination of a key value and a mapped value, and which allows for fast retrieval of individual elements based on their keys. unordered_map containers are faster than map containers to access individual elements by their key, although they are generally less efficient for range iteration through a subset of their elements. Unordered maps implement the direct access operator (operator[]) which allows for direct access of the mapped value using its key value as argument. Appropriate use of hashed containers can improve performance. An example of use of <unordered_map> : cplusplus.com. Another example with a custom class type as a key: stackoverflow.com Concurrency link ▶ C++11 threading libraries should be used through the Geant4 interface and not directly. C++11 provides support for multithreading in dedicated headers. Migration to C++11 will be done internally in Geant4 threading related core classes and definitions. In this way, they will be available through the Geant4 interface and should not be used directly. |