C++ Core Guidelines

27
C++ Core Guidelines and the Guideline Support Library Thomas Pollak

Transcript of C++ Core Guidelines

Page 1: C++ Core Guidelines

C++ Core Guidelinesand the Guideline Support Library

Thomas Pollak

Page 2: C++ Core Guidelines

1972:C

1983:Renamedto C++

What is it good for? C++ is very flexible and unfortunately also quite complicated Basically let’s us do everything, even if it is dangerous You can find a lot of “advices” on the web, which are just bad Bjarne Stroustrup: “Within C++ is a smaller, simpler, safer language struggling

to get out”

1979:C with Classes

1998:New ISO StandardC++98

2003:New ISO Standard C++03

2011:New ISO Standard C++11(inf. C+

+0x)

2014:New ISO Standard C++14

2017:New ISO Standard C++17

Bjarne StroustrupKen Thompson (left)Dennis Ritchie (right)

Page 3: C++ Core Guidelines

1972:C

1983:Renamedto C++

1979:C with Classes

1998:New ISO StandardC++98

2003:New ISO Standard C++03

2011:New ISO Standard C++11(inf. C+

+0x)

2014:New ISO Standard C++14

2017:New ISO Standard C++17

What is it good for?Use a subset of safe, modern C++ features and apply rules to avoid crashes, undefined behaviour and similar.

Core Guidelines

Page 4: C++ Core Guidelines

How can I use them?

https://github.com/isocpp/CppCoreGuidelines/blob/master/CppCoreGuidelines.md

Work in progress, sometimes controversial + you can also contribute! The document gives advices how to write good C++ code Supports you with a library, the Guideline Support Library (GSL) Tools that can check these rules

VisualStudio with an additional Nuget package (via the Analyze function) Clang-tidy (via command line)

Let’s take a look at some samples (not complete, nor ordered by importance)

Page 5: C++ Core Guidelines

Samples: P.1 Express ideas directly in codehttps://github.com/isocpp/CppCoreGuidelines/blob/master/CppCoreGuidelines.md#p1-express-ideas-directly-in-code

class Date {public: Month month() const; // do int month(); // don't // ...};

//don'tvoid f(vector<string>& v, string val){ int index = -1; for (int i = 0; i < v.size(); ++i) if (v[i] == val) { index = i;

break; } // ...}

//dovoid f(vector<string>& v, string val){ auto p = find(begin(v), end(v), val); // ...}

Page 6: C++ Core Guidelines

Samples: P.5 Prefer compile-time checking to run-time checkinghttps://github.com/isocpp/CppCoreGuidelines/blob/master/CppCoreGuidelines.md#p5-prefer-compile-time-checking-to-run-time-checking

//don’t

void read(int* p, int n); // read max n integers into *p

//do

void read(span<int> r); // read into the range of integers r

//the span<T> template is supplied via the GSL

Page 7: C++ Core Guidelines

Samples: I.4 Make interfaces precisely and strongly typedhttps://github.com/isocpp/CppCoreGuidelines/blob/master/CppCoreGuidelines.md#i4-make-interfaces-precisely-and-strongly-typed

void pass(void* data); // void* is suspicious, always

//don’tvoid draw_rect(int, int, int, int); // great opportunities for mistakes

draw_rect(p.x, p.y, 10, 20); // what does 10, 20 mean?

//dovoid draw_rectangle(Point top_left, Point bottom_right);void draw_rectangle(Point top_left, Size height_width);

draw_rectangle(p, Point{10, 20}); // two cornersdraw_rectangle(p, Size{10, 20}); // one corner and a (height, width) pair

Page 8: C++ Core Guidelines

Samples: I.6 Prefer Expects() for expressing preconditionshttps://github.com/isocpp/CppCoreGuidelines/blob/master/CppCoreGuidelines.md#i6-prefer-expects-for-expressing-preconditions

int area(int height, int width){ Expects(height > 0 && width > 0); // good if (height <= 0 || width <= 0) my_error(); // obscure // ...}

//Preconditions can be stated in many ways, including comments, if-//statements, and assert(). This can make them hard to distinguish from //ordinary code, hard to update, hard to manipulate by tools, and may //have the wrong semantics (do you always want to abort in debug mode //and check nothing in productions runs?).

//Preconditions should be part of the interface rather than part of the //implementation, but we don't yet have the language facilities to do //that.

//The Expects(cond) macro is supplied via the GSL

Page 9: C++ Core Guidelines

Samples: I.9 If an interface is a template, document its parameters using conceptshttps://github.com/isocpp/CppCoreGuidelines/blob/master/CppCoreGuidelines.md#i9-if-an-interface-is-a-template-document-its-parameters-using-concepts

template<typename Iter, typename Val>// requires InputIterator<Iter> && EqualityComparable<ValueType<Iter>>, Val>Iter find(Iter first, Iter last, Val v){ // ...}

//Note://Soon most compilers might be able to check requires clauses and you can //simply remove once the comment double slashes //.

Page 10: C++ Core Guidelines

Samples: I.11 Never transfer ownership by a raw pointerhttps://github.com/isocpp/CppCoreGuidelines/blob/master/CppCoreGuidelines.md#i11-never-transfer-ownership-by-a-raw-pointer-t

// don'tX* compute(args){ X* res = new X{}; // ... return res;}

//dovector<double> compute(args){ vector<double> res(10000); // ... return res;}

Page 11: C++ Core Guidelines

Samples: I.12 Declare a pointer that must not be null as not_nullhttps://github.com/isocpp/CppCoreGuidelines/blob/master/CppCoreGuidelines.md#i12-declare-a-pointer-that-must-not-be-null-as-not_null

int length(const char* p); // is length(nullptr) valid?

length(nullptr); // OK?

int length(not_null<const char*> p);// better: p cannot be nullptr

int length(const char* p); // we must assume that p can be nullptr

//the not_null<T> template is supplied via the GSL

Page 12: C++ Core Guidelines

Samples: F.3 Keep functions short and simplehttps://github.com/isocpp/CppCoreGuidelines/blob/master/CppCoreGuidelines.md#f3-keep-functions-short-and-simple

//There is a lot of discussion how many lines of code are a “good” size for//function/method. Usually if it does not fit on the screen any more you //should start to think how to make it simpler.

//Without naming a reference I hope that we can agree that anything beyond //250 lines is maybe too long. People also argue that loc is not a good //way of measuring code complexity (however it is easy to check).

Page 13: C++ Core Guidelines

Samples: F.51 Where there is a choice, prefer default arguments over overloadinghttps://github.com/isocpp/CppCoreGuidelines/blob/master/CppCoreGuidelines.md#f51-where-there-is-a-choice-prefer-default-arguments-over-overloading

//so this is bettervoid print(const string& s, format f = {});

//thanvoid print(const string& s); // use default formatvoid print(const string& s, format f);

//There is no guarantee that a set of overloaded functions all implement the same semantics.//The use of default arguments can avoid code replication.

Page 14: C++ Core Guidelines

Samples: C.20 If you can avoid defining default operations, dohttps://github.com/isocpp/CppCoreGuidelines/blob/master/CppCoreGuidelines.md#c20-if-you-can-avoid-defining-default-operations-do

struct Named_map {public: // ... no default operations declared ...private: string name; map<int, int> rep;};

Named_map nm; // default constructNamed_map nm2 {nm}; // copy construct

//defining no constructor is also known as “rule of zero”

Page 15: C++ Core Guidelines

Samples: C.45 Don't define a default constructor that only initializes data members; use in-class member initializers insteadhttps://github.com/isocpp/CppCoreGuidelines/blob/master/CppCoreGuidelines.md#c45-dont-define-a-default-constructor-that-only-initializes-data-members-use-in-class-member-initializers-instead

class X1 { // BAD: doesn't use member initializers string s; int i;public: X1() :s{"default"}, i{1} { } // ...};

class X2 { string s = "default"; int i = 1;public: // use compiler-generated default constructor // ...};

Page 16: C++ Core Guidelines

Samples: C.131 Avoid trivial getters and settershttps://github.com/isocpp/CppCoreGuidelines/blob/master/CppCoreGuidelines.md#c131-avoid-trivial-getters-and-setters

class point { int x; int y;public: point(int xx, int yy) : x{xx}, y{yy} { } int get_x() { return x; } void set_x(int xx) { x = xx; } int get_y() { return y; } void set_y(int yy) { y = yy; } // no behavioral member functions};

//basically the same like this:struct point { int x = 0; int y = 0;};

Page 17: C++ Core Guidelines

Samples: C.140 Do not provide different default arguments for a virtual function and an overriderhttps://github.com/isocpp/CppCoreGuidelines/blob/master/CppCoreGuidelines.md#c140-do-not-provide-different-default-arguments-for-a-virtual-function-and-an-overrider

class base {public: virtual int multiply(int value, int factor = 2) = 0;};

class derived : public base {public: int multiply(int value, int factor = 10) override;};

derived dr;base& bs = dr;

bs.multiply(10); // these two calls will call the same function butdr.multiply(10); // with different arguments and so different results

Page 18: C++ Core Guidelines

Samples: Enum.1 Prefer enums over macroshttps://github.com/isocpp/CppCoreGuidelines/blob/master/CppCoreGuidelines.md#enum-enumerations

//don’t//Webcolors in header x#define RED 0xFF0000#define GREEN 0x00FF00#define BLUE 0x0000FF

//also bad//Productinfo in header y#define RED 0#define PURPLE 1#define BLUE 2

int webby = BLUE; // wrong header could now mean “webby == 2”

//doenum class Webcolor { red = 0xFF0000, green = 0x00FF00, blue = 0x0000FF };enum class Productinfo { red = 0, purple = 1, blue = 2 };

int webby = blue; // error: be specific (this is a class enum, not a plain enum)Webcolor webby = Webcolor::blue;

Page 19: C++ Core Guidelines

Samples: ES.5 Keep scopes smallhttps://github.com/isocpp/CppCoreGuidelines/blob/master/CppCoreGuidelines.md#es5-keep-scopes-small

void use(){ int i;

// bad: i is needlessly accessible after loop for (i = 0; i < 20; ++i) { /* ... */ } // no intended use of i here

// good: i is local to for-loop for (int i = 0; i < 20; ++i) { /* ... */ }

// good: pc is local to if-statement if (auto pc = dynamic_cast<Circle*>(ps)) { // ... deal with Circle ... } else { // ... handle error ... }}

Page 20: C++ Core Guidelines

Samples: ES.20 Always initialize an objecthttps://github.com/isocpp/CppCoreGuidelines/blob/master/CppCoreGuidelines.md#es20-always-initialize-an-object

void use(int arg){ int i; // bad: uninitialized variable // ... i = 7; // initialize i}

//No, i = 7 does not initialize i; it assigns to it.//Also, i can be read in the ... part. Better:

void use(int arg){ int i = 7; // OK: initialized string s; // OK: default initialized // ...}

Page 21: C++ Core Guidelines

Samples: ES.50 Don't cast away consthttps://github.com/isocpp/CppCoreGuidelines/blob/master/CppCoreGuidelines.md#es50-dont-cast-away-const

void bad_function(const MyClass* cInstance) { ... if(cInstance.max_bytes>MAX_VAL) { //bad sample MyClass* instance = const_cast<Myclass*>(cInstance);

++instance.max_bytes; }}

//if really needed MyClass can be change, eg:class MyCLass {public: mutable std::int32_t max_bytes; //only a subset can be changed, //not the whole class ...}

Page 22: C++ Core Guidelines

Samples: P.8 Don't leak any resourceshttps://github.com/isocpp/CppCoreGuidelines/blob/master/CppCoreGuidelines.md#p8-dont-leak-any-resources

void f(const char* name){ FILE* input = fopen(name, "r"); // ... if (something) return; // bad: A file handle might leak // ... fclose(input);}

//Prefer RAII:

void f(const char* name){ ifstream input {name}; // ... if (something) return; // OK: no leak // ...}

Page 23: C++ Core Guidelines

Samples: R.5 Don't heap-allocate unnecessarilyhttps://github.com/isocpp/CppCoreGuidelines/blob/master/CppCoreGuidelines.md#r5-dont-heap-allocate-unnecessarily

void f(int n){ auto p = new Gadget{n}; // ... delete p;}

//Instead, use a local variable:

void f(int n){ Gadget g{n}; // ...}

Page 24: C++ Core Guidelines

Samples: R.11 Avoid calling new and delete explicitlyhttps://github.com/isocpp/CppCoreGuidelines/blob/master/CppCoreGuidelines.md#r11-avoid-calling-new-and-delete-explicitly

auto myObject = new MyObject{“MyObj”}; //bad

auto myObjectUnique = std::make_unique<MyObject>(“MyObj”); //better

//in case you need to share the resource in your application:auto myObjectShared = std::make_shared<MyObject>(“MyObj”);

//most libraries come with some kind of smart pointers//doing more or less the same (if you cannot use STL)

Page 25: C++ Core Guidelines

Samples: T.1 Use templates to raise the level o abstraction of codehttps://github.com/isocpp/CppCoreGuidelines/blob/master/CppCoreGuidelines.md#t1-use-templates-to-raise-the-level-of-abstraction-of-code

template<typename T, typename A> // requires Arithmetic<T>A sum(vector<T>& v, A s){ for (auto x : v) s += x; return s;}

//For additional generality and reusability, we could also use a //more general Container or Range concept instead of committing to //only one container, vector.

Page 26: C++ Core Guidelines

How can I use the GSL?

The GSL has a public specification that can be implemented by anyone Currently we can use the Microsoft implementation

https://github.com/Microsoft/GSL There are other versions too (eg. for C++11), however not updated very

frequently

Page 27: C++ Core Guidelines

Tools to enforce the rules

A) CppCoreCheck can be installed as package to any C++ project via the following command in the VS package manager console (an then run “Analyze”):

B) Install latest clang from http://llvm.org/releases/download.html and use clang-tidy:

(Unfortunately problematic with Windows headers)

Both tools check more than only the C++ Core Guidelines – it’s a good idea in general to use a static code analyzer to avoid errors.

PM> Install-Package Microsoft.CppCoreCheck

$ clang-tidy SOURCE --checks=all -extra-arg=-std=c++14 –extra-arg=-fexceptions