diff --git a/CppCoreGuidelines.md b/CppCoreGuidelines.md index 12896ea..bcad2a4 100644 --- a/CppCoreGuidelines.md +++ b/CppCoreGuidelines.md @@ -550,7 +550,7 @@ Code clarity and performance. You don't need to write error handlers for errors void initializer(Int x) // Int is an alias used for integers { - static_assert(sizeof(Int) >= 4); // do: compile-time check + static_assert(sizeof(Int) >= 4); // do: compile-time check int bits = 0; // don't: avoidable code for (Int i = 1; i; i <<= 1) @@ -765,10 +765,13 @@ There are cases where checking early is dumb because you may not ever need the v class Jet { // Physics says: e*e < x*x + y*y + z*z - float fx, fy, fz, fe; + float x; + float y; + float z; + float e; public: Jet(float x, float y, float z, float e) - :fx(x), fy(y), fz(z), fe(e) + :x(x), y(y), z(z), e(e) { // Should I check here that the values are physically meaningful? } @@ -866,7 +869,7 @@ Time and space that you spend well to achieve a goal (e.g., speed of development X waste(const char* p) { - if (p == nullptr) throw Nullptr_error{}; + if (p == nullptr) throw Nullptr_error{}; int n = strlen(p); auto buf = new char[n]; if (buf == nullptr) throw Allocation_error{}; @@ -875,7 +878,7 @@ Time and space that you spend well to achieve a goal (e.g., speed of development X x; x.ch = 'a'; x.s = string(n); // give x.s space for *ps - for (int i = 0; i < x.s.size(); ++i) x.s[i] = buf[i]; // copy buf into x.s + for (int i = 0; i < x.s.size(); ++i) x.s[i] = buf[i]; // copy buf into x.s delete buf; return x; } @@ -2239,8 +2242,8 @@ When copying is cheap, nothing beats the simplicity and safety of copying, and f For advanced uses (only), where you really need to optimize for rvalues passed to "input-only" parameters: -* If the function is going to unconditionally move from the argument, take it by `&&`. See [F.21](#Rf-consume). -* If the function is going to keep a copy of the argument, in addition to passing by `const&` add an overload that passes the parameter by `&&` and in the body `std::move`s it to its destination. Essentially this overloads a "consume"; see [F.21](#Rf-consume). +* If the function is going to unconditionally move from the argument, take it by `&&`. See [F.18](#Rf-consume). +* If the function is going to keep a copy of the argument, in addition to passing by `const&` add an overload that passes the parameter by `&&` and in the body `std::move`s it to its destination. Essentially this overloads a "consume"; see [F.18](#Rf-consume). * In special cases, such as multiple "input + copy" parameters, consider using perfect forwarding. See [F.19](#Rf-forward). ##### Example @@ -2343,7 +2346,7 @@ Unique owner types that are move-only and cheap-to-move, such as `unique_ptr`, c If the object is to be passed onward to other code and not directly used by this function, we want to make this function agnostic to the argument `const`-ness and rvalue-ness. -In that case, and only that case, make the parameter `TP&&` where `TP` is a template type parameter -- it both *ignores* and *preserves* `const`-ness and rvalue-ness. Therefore any code that uses a `T&&` is implicitly declaring that it itself doesn't care about the variable's `const`-ness and rvalue-ness (because it is ignored), but that intends to pass the value onward to other code that does care about `const`-ness and rvalue-ness (because it is preserved). When used as a parameter `TP&&` is safe because any temporary objects passed from the caller will live for the duration of the function call. A parameter of type `TP&&` should essentially always be passed onward via `std::forward` in the body of the function. +In that case, and only that case, make the parameter `TP&&` where `TP` is a template type parameter -- it both *ignores* and *preserves* `const`-ness and rvalue-ness. Therefore any code that uses a `TP&&` is implicitly declaring that it itself doesn't care about the variable's `const`-ness and rvalue-ness (because it is ignored), but that intends to pass the value onward to other code that does care about `const`-ness and rvalue-ness (because it is preserved). When used as a parameter `TP&&` is safe because any temporary objects passed from the caller will live for the duration of the function call. A parameter of type `TP&&` should essentially always be passed onward via `std::forward` in the body of the function. ##### Example @@ -2419,7 +2422,7 @@ And yes, C++ does have multiple return values, by convention of using a `tuple`, tuple f(const string& input) // GOOD: self-documenting { // ... - return make_tuple(something(), status); + return make_tuple(status, something()); } In fact, C++98's standard library already used this convenient feature, because a `pair` is like a two-element `tuple`. @@ -2510,7 +2513,7 @@ Clarity. Making it clear that a test for null isn't needed. void computer(not_null> p) { - if (0 < p.size()) { // bad: redundant test + if (0 < p.size()) { // bad: redundant test // ... } } @@ -2533,7 +2536,7 @@ Informal/non-explicit ranges are a source of errors. ##### Example - X* find(span r, const X& v); // find v in r + X* find(span r, const X& v); // find v in r vector vec; // ... @@ -2600,7 +2603,7 @@ Using `unique_ptr` is the cheapest way to pass a pointer safely. case kTriangle: return make_unique(is); // ... - } + } } ##### Note @@ -2626,7 +2629,7 @@ Using `std::shared_ptr` is the standard way to represent shared ownership. That std::thread t2 {shade, args2, bottom_left, im}; std::thread t3 {shade, args3, bottom_right, im}; - // detach treads + // detach threads // last thread to finish deletes the image ##### Note @@ -3193,11 +3196,11 @@ You need a reason (use cases) for using a hierarchy. void use() { - Point1 p11 {1, 2}; // make an object on the stack - Point1 p12 {p11}; // a copy + Point1 p11 {1, 2}; // make an object on the stack + Point1 p12 {p11}; // a copy - auto p21 = make_unique(1, 2); // make an object on the free store - auto p22 = p21.clone(); // make a copy + auto p21 = make_unique(1, 2); // make an object on the free store + auto p22 = p21.clone(); // make a copy // ... } @@ -3344,8 +3347,8 @@ It's the simplest and gives the cleanest semantics. map rep; }; - Named_map nm; // default construct - Named_map nm2 {nm}; // copy construct + Named_map nm; // default construct + Named_map nm2 {nm}; // copy construct Since `std::map` and `string` have all the special functions, no further work is needed. @@ -3366,7 +3369,7 @@ The semantics of the special functions are closely related, so if one needs to b ##### Example, bad - struct M2 { // bad: incomplete set of default operations + struct M2 { // bad: incomplete set of default operations public: // ... // ... no copy or move operations ... @@ -3380,7 +3383,7 @@ The semantics of the special functions are closely related, so if one needs to b M2 x; M2 y; // ... - x = y; // the default assignment + x = y; // the default assignment // ... } @@ -3416,7 +3419,7 @@ Users will be surprised if copy/move construction and copy/move assignment do lo ##### Example, bad - class Silly { // BAD: Inconsistent copy operations + class Silly { // BAD: Inconsistent copy operations class Impl { // ... }; @@ -3454,14 +3457,14 @@ Only define a non-default destructor if a class needs to execute code that is no ##### Example template - struct final_action { // slightly simplified + struct final_action { // slightly simplified A act; final_action(F a) :act{a} {} ~final_action() { act(); } }; template - final_action finally(A act) // deduce action type + final_action finally(A act) // deduce action type { return final_action{a}; } @@ -3470,7 +3473,7 @@ Only define a non-default destructor if a class needs to execute code that is no { auto act = finally([]{ cout << "Exit test\n"; }); // establish exit action // ... - if (something) return; // act done here + if (something) return; // act done here // ... } // act done here @@ -3485,7 +3488,7 @@ There are two general categories of classes that need a user-defined destructor: ##### Example, bad - class Foo { // bad; use the default destructor + class Foo { // bad; use the default destructor public: // ... ~Foo() { s = ""; i = 0; vi.clear(); } // clean up @@ -3518,7 +3521,7 @@ For resources represented as classes with a complete set of default operations, ##### Example class X { - ifstream f; // may own a file + ifstream f; // may own a file // ... no default operations defined or =deleted ... }; @@ -3526,8 +3529,8 @@ For resources represented as classes with a complete set of default operations, ##### Example, bad - class X2 { // bad - FILE* f; // may own a file + class X2 { // bad + FILE* f; // may own a file // ... no default operations defined or =deleted ... }; @@ -3595,7 +3598,7 @@ Consider a `T*` a possible owner and therefore suspect. template class Smart_ptr { - T* p; // BAD: vague about ownership of *p + T* p; // BAD: vague about ownership of *p // ... public: // ... no user-defined default operations ... @@ -3603,14 +3606,14 @@ Consider a `T*` a possible owner and therefore suspect. void use(Smart_ptr p1) { - auto p2 = p1; // error: p2.p leaked (if not nullptr and not owned by some other code) + auto p2 = p1; // error: p2.p leaked (if not nullptr and not owned by some other code) } Note that if you define a destructor, you must define or delete [all default operations](#Rc-five): template class Smart_ptr2 { - T* p; // BAD: vague about ownership of *p + T* p; // BAD: vague about ownership of *p // ... public: // ... no user-defined copy operations ... @@ -3619,14 +3622,14 @@ Note that if you define a destructor, you must define or delete [all default ope void use(Smart_ptr p1) { - auto p2 = p1; // error: double deletion + auto p2 = p1; // error: double deletion } The default copy operation will just copy the `p1.p` into `p2.p` leading to a double destruction of `p1.p`. Be explicit about ownership: template class Smart_ptr3 { - owner* p; // OK: explicit about ownership of *p + owner* p; // OK: explicit about ownership of *p // ... public: // ... @@ -3636,7 +3639,7 @@ The default copy operation will just copy the `p1.p` into `p2.p` leading to a do void use(Smart_ptr3 p1) { - auto p2 = p1; // error: double deletion + auto p2 = p1; // error: double deletion } ##### Note @@ -3750,7 +3753,7 @@ If the interface allows destroying, it should be safe to do so. A destructor must be nonprivate or it will prevent using the type : class X { - ~X(); // private destructor + ~X(); // private destructor // ... }; @@ -3845,7 +3848,7 @@ That's what constructors are for. ##### Example - class Date { // a Date represents a valid date + class Date { // a Date represents a valid date // in the January 1, 1900 to December 31, 2100 range Date(int dd, int mm, int yy) :d{dd}, m{mm}, y{yy} @@ -3880,7 +3883,7 @@ The C++11 initializer list rule eliminates the need for many constructors. For e struct Rec2{ string s; int i; - Rec2(const string& ss, int ii = 0) :s{ss}, i{ii} {} // redundant + Rec2(const string& ss, int ii = 0) :s{ss}, i{ii} {} // redundant }; Rec r1 {"Foo", 7}; @@ -3904,21 +3907,21 @@ A constructor establishes the invariant for a class. A user of a class should be ##### Example, bad class X1 { - FILE* f; // call init() before any other function + FILE* f; // call init() before any other function // ... public: X1() {} - void init(); // initialize f - void read(); // read from f + void init(); // initialize f + void read(); // read from f // ... }; void f() { X1 file; - file.read(); // crash or bad read! + file.read(); // crash or bad read! // ... - file.init(); // too late + file.init(); // too late // ... } @@ -3940,50 +3943,50 @@ Leaving behind an invalid object is asking for trouble. ##### Example class X2 { - FILE* f; // call init() before any other function + FILE* f; // call init() before any other function // ... public: X2(const string& name) :f{fopen(name.c_str(), "r")} { - if (f == nullptr) throw runtime_error{"could not open" + name}; + if (f == nullptr) throw runtime_error{"could not open" + name}; // ... } - void read(); // read from f + void read(); // read from f // ... }; void f() { X2 file {"Zeno"}; // throws if file isn't open - file.read(); // fine + file.read(); // fine // ... } ##### Example, bad - class X3 { // bad: the constructor leaves a non-valid object behind - FILE* f; // call init() before any other function + class X3 { // bad: the constructor leaves a non-valid object behind + FILE* f; // call init() before any other function bool valid; // ... public: X3(const string& name) :f{fopen(name.c_str(), "r")}, valid{false} { - if (f) valid = true; + if (f) valid = true; // ... } void is_valid() { return valid; } - void read(); // read from f + void read(); // read from f // ... }; void f() { X3 file {"Heraclides"}; - file.read(); // crash or bad read! + file.read(); // crash or bad read! // ... if (is_valid()) { file.read(); @@ -4031,8 +4034,8 @@ Many language and library facilities rely on default constructors to initialize // ... }; - vector vd1(1000); // default Date needed here - vector vd2(1000, Date{Month::october, 7, 1885}); // alternative + vector vd1(1000); // default Date needed here + vector vd2(1000, Date{Month::october, 7, 1885}); // alternative The default constructor is only auto-generated if there is no user-declared constructor, hence it's impossible to initialize the vector `vd1` in the example above. @@ -4070,16 +4073,16 @@ A class with members that all have default constructors implicitly gets a defaul Beware that built-in types are not properly default constructed: struct X { - string s; - int i; + string s; + int i; }; void f() { - X x; // x.s is initialized to the empty string; x.i is uninitialized + X x; // x.s is initialized to the empty string; x.i is uninitialized - cout << x.s << ' ' << x.i << '\n'; - ++x.i; + cout << x.s << ' ' << x.i << '\n'; + ++x.i; } Statically allocated objects of built-in types are by default initialized to `0`, but local built-in variables are not. @@ -4088,8 +4091,8 @@ Thus, code like the example above may appear to work, but it relies on undefined Assuming that you want initialization, an explicit default initialization can help: struct X { - string s; - int i {}; // default initialize (to 0) + string s; + int i {}; // default initialize (to 0) }; @@ -4106,7 +4109,7 @@ Being able to set a value to "the default" without operations that might fail si ##### Example, problematic template - class Vector0 { // elem points to space-elem element allocated using new + class Vector0 { // elem points to space-elem element allocated using new public: Vector0() :Vector0{0} {} Vector0(int n) :elem{new T[n]}, space{elem + n}, last{elem} {} @@ -4124,9 +4127,9 @@ For example, `Vector0 v(100)` costs 100 allocations. ##### Example template - class Vector1 { // elem is nullptr or elem points to space-elem element allocated using new + class Vector1 { // elem is nullptr or elem points to space-elem element allocated using new public: - Vector1() noexcept {} // sets the representation to {nullptr, nullptr, nullptr}; doesn't throw + Vector1() noexcept {} // sets the representation to {nullptr, nullptr, nullptr}; doesn't throw Vector1(int n) :elem{new T[n]}, space{elem + n}, last{elem} {} // ... private: @@ -4183,11 +4186,11 @@ To avoid unintended conversions. class String { // ... public: - String(int); // BAD + String(int); // BAD // ... }; - String s = 10; // surprise: string of size 10 + String s = 10; // surprise: string of size 10 ##### Exception @@ -4196,11 +4199,11 @@ If you really want an implicit conversion from the constructor argument type to class Complex { // ... public: - Complex(double d); // OK: we want a conversion from d to {d, 0} + Complex(double d); // OK: we want a conversion from d to {d, 0} // ... }; - Complex z = 10.7; // unsurprising conversion + Complex z = 10.7; // unsurprising conversion **See also**: [Discussion of implicit conversions](#Ro-conversion). @@ -4220,7 +4223,7 @@ To minimize confusion and errors. That is the order in which the initialization int m1; int m2; public: - Foo(int x) :m2{x}, m1{++x} { } // BAD: misleading initializer order + Foo(int x) :m2{x}, m1{++x} { } // BAD: misleading initializer order // ... }; @@ -4240,13 +4243,13 @@ Makes it explicit that the same value is expected to be used in all constructors ##### Example, bad - class X { // BAD + class X { // BAD int i; string s; int j; public: - X() :i{666}, s{"qqq"} { } // j is uninitialized - X(int ii) :i{ii} {} // s is "" and j is uninitialized + X() :i{666}, s{"qqq"} { } // j is uninitialized + X(int ii) :i{ii} {} // s is "" and j is uninitialized // ... }; @@ -4259,20 +4262,20 @@ How would a maintainer know whether `j` was deliberately uninitialized (probably string s {"qqq"}; int j {0}; public: - X2() = default; // all members are initialized to their defaults - X2(int ii) :i{ii} {} // s and j initialized to their defaults + X2() = default; // all members are initialized to their defaults + X2(int ii) :i{ii} {} // s and j initialized to their defaults // ... }; **Alternative**: We can get part of the benefits from default arguments to constructors, and that is not uncommon in older code. However, that is less explicit, causes more arguments to be passed, and is repetitive when there is more than one constructor: - class X3 { // BAD: inexplicit, argument passing overhead + class X3 { // BAD: inexplicit, argument passing overhead int i; string s; int j; public: X3(int ii = 666, const string& ss = "qqq", int jj = 0) - :i{ii}, s{ss}, j{jj} { } // all members are initialized to their defaults + :i{ii}, s{ss}, j{jj} { } // all members are initialized to their defaults // ... }; @@ -4289,7 +4292,7 @@ An initialization explicitly states that initialization, rather than assignment, ##### Example, good - class A { // Good + class A { // Good string s1; public: A() : s1{"Hello, "} { } // GOOD: directly construct @@ -4298,17 +4301,17 @@ An initialization explicitly states that initialization, rather than assignment, ##### Example, bad - class B { // BAD + class B { // BAD string s1; public: B() { s1 = "Hello, "; } // BAD: default constructor followed by assignment // ... }; - class C { // UGLY, aka very bad + class C { // UGLY, aka very bad int* p; public: - C() { cout << *p; p = new int{10}; } // accidental use before initialized + C() { cout << *p; p = new int{10}; } // accidental use before initialized // ... }; @@ -4325,7 +4328,7 @@ If the state of a base class object must depend on the state of a derived part o B() { // ... - f(); // BAD: virtual call in constructor + f(); // BAD: virtual call in constructor // ... } @@ -4380,7 +4383,7 @@ To avoid repetition and accidental differences. ##### Example, bad - class Date { // BAD: repetitive + class Date { // BAD: repetitive int d; Month m; int y; @@ -4447,7 +4450,7 @@ If you need those constructors for a derived class, re-implementing them is tedi }; Rec2 r {"foo", 7}; - int val = r.x; // uninitialized + int val = r.x; // uninitialized ##### Enforcement @@ -4471,7 +4474,7 @@ It is simple and efficient. If you want to optimize for rvalues, provide an over public: Foo& operator=(const Foo& x) { - auto tmp = x; // GOOD: no need to check for self-assignment (other than performance) + auto tmp = x; // GOOD: no need to check for self-assignment (other than performance) std::swap(*this, tmp); return *this; } @@ -4482,8 +4485,8 @@ It is simple and efficient. If you want to optimize for rvalues, provide an over Foo b; Foo f(); - a = b; // assign lvalue: copy - a = f(); // assign rvalue: potentially move + a = b; // assign lvalue: copy + a = f(); // assign rvalue: potentially move ##### Note @@ -4537,11 +4540,11 @@ After a copy `x` and `y` can be independent objects (value semantics, the way no ##### Example - class X { // OK: value semantics + class X { // OK: value semantics public: X(); - X(const X&); // copy X - void modify(); // change the value of X + X(const X&); // copy X + void modify(); // change the value of X // ... ~X() { delete[] p; } private: @@ -4564,7 +4567,7 @@ After a copy `x` and `y` can be independent objects (value semantics, the way no X y = x; if (x != y) throw Bad{}; x.modify(); - if (x == y) throw Bad{}; // assume value semantics + if (x == y) throw Bad{}; // assume value semantics ##### Example @@ -4625,7 +4628,7 @@ The default assignment generated from members that handle self-assignment correc Bar b; // ... - b = b; // correct and efficient + b = b; // correct and efficient ##### Note @@ -4639,7 +4642,7 @@ You can handle self-assignment by explicitly testing for self-assignment, but of // ... }; - Foo& Foo::operator=(const Foo& a) // OK, but there is a cost + Foo& Foo::operator=(const Foo& a) // OK, but there is a cost { if (this == &a) return *this; s = a.s; @@ -4652,7 +4655,7 @@ However, what if we do one self-assignment per million assignments? That's about a million redundant tests (but since the answer is essentially always the same, the computer's branch predictor will guess right essentially every time). Consider: - Foo& Foo::operator=(const Foo& a) // simpler, and probably much better + Foo& Foo::operator=(const Foo& a) // simpler, and probably much better { s = a.s; i = a.i; @@ -4690,11 +4693,11 @@ That is the generally assumed semantics. After `x=std::move(y)` the value of `x` ##### Example template - class X { // OK: value semantics + class X { // OK: value semantics public: X(); - X(X&& a); // move X - void modify(); // change the value of X + X(X&& a); // move X + void modify(); // change the value of X // ... ~X() { delete[] p; } private: @@ -4704,9 +4707,9 @@ That is the generally assumed semantics. After `x=std::move(y)` the value of `x` X::X(X&& a) - :p{a.p}, sz{a.sz} // steal representation + :p{a.p}, sz{a.sz} // steal representation { - a.p = nullptr; // set to "empty" + a.p = nullptr; // set to "empty" a.sz = 0; } @@ -4715,7 +4718,7 @@ That is the generally assumed semantics. After `x=std::move(y)` the value of `x` X x{}; // ... X y = std::move(x); - x = X{}; // OK + x = X{}; // OK } // OK: x can be destroyed ##### Note @@ -4807,8 +4810,8 @@ These copy operations do not throw. template class Vector2 { // ... - Vector2(Vector2&& a) { *this = a; } // just use the copy - Vector2& operator=(Vector2&& a) { *this = a; } // just use the copy + Vector2(Vector2&& a) { *this = a; } // just use the copy + Vector2& operator=(Vector2&& a) { *this = a; } // just use the copy // ... public: T* elem; @@ -4924,15 +4927,15 @@ In a few cases, a default operation is not desirable. class Immortal { public: - ~Immortal() = delete; // do not allow destruction + ~Immortal() = delete; // do not allow destruction // ... }; void use() { - Immortal ugh; // error: ugh cannot be destroyed + Immortal ugh; // error: ugh cannot be destroyed Immortal* p = new Immortal{}; - delete p; // error: cannot destroy *p + delete p; // error: cannot destroy *p } ##### Example @@ -4945,19 +4948,19 @@ A `unique_ptr` can be moved, but not copied. To achieve that its copy operations constexpr unique_ptr() noexcept; explicit unique_ptr(pointer p) noexcept; // ... - unique_ptr(unique_ptr&& u) noexcept; // move constructor + unique_ptr(unique_ptr&& u) noexcept; // move constructor // ... unique_ptr(const unique_ptr&) = delete; // disable copy from lvalue // ... }; - unique_ptr make(); // make "something" and return it by moving + unique_ptr make(); // make "something" and return it by moving void f() { unique_ptr pi {}; - auto pi2 {pi}; // error: no move constructor from lvalue - auto pi3 {make()}; // OK, move: the result of make() is an rvalue + auto pi2 {pi}; // error: no move constructor from lvalue + auto pi3 {make()}; // OK, move: the result of make() is an rvalue } ##### Enforcement @@ -5044,7 +5047,7 @@ Providing a nonmember `swap` function in the same namespace as your type for cal void swap(My_vector& x, My_vector& y) { - auto tmp = x; // copy elements + auto tmp = x; // copy elements x = y; y = tmp; } @@ -5133,12 +5136,12 @@ It is really hard to write a foolproof and useful `==` for a hierarchy. B b = ... D d = ... - b == d; // compares name and number, ignores d's character - d == b; // error: no == defined + b == d; // compares name and number, ignores d's character + d == b; // error: no == defined D d2; - d == d2; // compares name, number, and character + d == d2; // compares name, number, and character B& b2 = d2; - b2 == d; // compares name and number, ignores d2's and d's character + b2 == d; // compares name and number, ignores d2's and d's character Of course there are ways of making `==` work in a hierarchy, but the naive approaches do not scale @@ -5353,14 +5356,14 @@ A class with a virtual function is usually (and in general) used via a pointer t // ... no destructor ... }; - struct D : B { // bad: class with a resource derived from a class without a virtual destructor + struct D : B { // bad: class with a resource derived from a class without a virtual destructor string s {"default"}; }; void use() { B* p = new D; - delete p; // leak the string + delete p; // leak the string } ##### Note @@ -5388,9 +5391,9 @@ Readability. Detection of mistakes. Explicit `override` allows the compiler to c }; struct D : B { - void f1(int); // warn: D::f1() hides B::f1() - void f2(int); // warn: no explicit override - void f3(double); // warn: D::f3() hides B::f3() + void f1(int); // warn: D::f1() hides B::f1() + void f2(int); // warn: no explicit override + void f3(double); // warn: D::f3() hides B::f3() // ... }; @@ -5487,10 +5490,10 @@ A virtual function ensures code replication in a templated hierarchy. class Vector { public: // ... - virtual int size() const { return sz; } // bad: what good could a derived class do? + virtual int size() const { return sz; } // bad: what good could a derived class do? private: - T* elem; // the elements - int sz; // number of elements + T* elem; // the elements + int sz; // number of elements }; This kind of "vector" isn't meant to be used as a base class at all. @@ -5703,14 +5706,14 @@ If you have a class with a virtual function, you don't (in general) know which c void use(B b) { D d; - B b2 = d; // slice + B b2 = d; // slice B b3 = b; } void use2() { D d; - use(d); // slice + use(d); // slice } Both `d`s are sliced. @@ -5722,7 +5725,7 @@ You can safely access a named polymorphic object in the scope of its definition, void use3() { D d; - d.f(); // OK + d.f(); // OK } ##### Enforcement @@ -5737,12 +5740,12 @@ Flag all slicing. ##### Example - struct B { // an interface + struct B { // an interface virtual void f(); virtual void g(); }; - struct D : B { // a wider interface + struct D : B { // a wider interface void f() override; virtual void h(); }; @@ -5883,9 +5886,9 @@ It also ensures exception safety in complex expressions. ##### Example - unique_ptr p {new{7}}; // OK: but repetitive + unique_ptr p {new{7}}; // OK: but repetitive - auto q = make_unique(7); // Better: no repetition of Foo + auto q = make_unique(7); // Better: no repetition of Foo // Not exception-safe: the compiler may interleave the computations of arguments as follows: // @@ -5914,9 +5917,9 @@ It also gives an opportunity to eliminate a separate allocation for the referenc ##### Example - shared_ptr p {new{7}}; // OK: but repetitive; and separate allocations for the Foo and shared_ptr's use count + shared_ptr p {new{7}}; // OK: but repetitive; and separate allocations for the Foo and shared_ptr's use count - auto q = make_shared(7); // Better: no repetition of Foo; one object + auto q = make_shared(7); // Better: no repetition of Foo; one object ##### Enforcement @@ -5937,10 +5940,10 @@ Subscripting the resulting base pointer will lead to invalid object access and p void use(B*); D a[] = { {1, 2}, {3, 4}, {5, 6} }; - B* p = a; // bad: a decays to &a[0] which is converted to a B* - p[1].x = 7; // overwrite D[0].y + B* p = a; // bad: a decays to &a[0] which is converted to a B* + p[1].x = 7; // overwrite D[0].y - use(a); // bad: a decays to &a[0] which is converted to a B* + use(a); // bad: a decays to &a[0] which is converted to a B* ##### Enforcement @@ -5970,7 +5973,7 @@ Minimize surprises. ##### Example, bad - X operator+(X a, X b) { return a.v - b.v; } // bad: makes + subtract + X operator+(X a, X b) { return a.v - b.v; } // bad: makes + subtract ???. Non-member operators: namespace-level definition (traditional?) vs friend definition (as used by boost.operator, limits lookup to ADL only) @@ -6029,13 +6032,13 @@ Having the same name for logically different functions is confusing and leads to Consider: - void open_gate(Gate& g); // remove obstacle from garage exit lane - void fopen(const char*name, const char* mode); // open file + void open_gate(Gate& g); // remove obstacle from garage exit lane + void fopen(const char*name, const char* mode); // open file The two operations are fundamentally different (and unrelated) so it is good that their names differ. Conversely: - void open(Gate& g); // remove obstacle from garage exit lane - void open(const char*name, const char* mode ="r"); // open file + void open(Gate& g); // remove obstacle from garage exit lane + void open(const char*name, const char* mode ="r"); // open file The two operations are still fundamentally different (and unrelated) but the names have been reduced to their (common) minimum, opening opportunities for confusion. Fortunately, the type system will catch many such mistakes. @@ -6063,7 +6066,7 @@ just to gain a minor convenience. ##### Example, bad - class String { // handle ownership and access to a sequence of characters + class String { // handle ownership and access to a sequence of characters // ... String(czstring p); // copy from *p to *(this->elem) // ... @@ -6146,12 +6149,12 @@ You can overload by defining two different lambdas with the same name. void f(int); void f(double); - auto f = [](char); // error: cannot overload variable and function + auto f = [](char); // error: cannot overload variable and function auto g = [](int) { /* ... */ }; - auto g = [](double) { /* ... */ }; // error: cannot overload variables + auto g = [](double) { /* ... */ }; // error: cannot overload variables - auto h = [](auto) { /* ... */ }; // OK + auto h = [](auto) { /* ... */ }; // OK ##### Enforcement @@ -6474,10 +6477,10 @@ Such containers and views hold sufficient information to do range checking. ##### Example, bad - void f(int* p, int n) // n is the number of elements in p[] + void f(int* p, int n) // n is the number of elements in p[] { // ... - p[2] = 7; // bad: subscript raw pointer + p[2] = 7; // bad: subscript raw pointer // ... } @@ -6486,7 +6489,7 @@ Use a `span` instead. ##### Example - void g(int* p, int fmt) // print *p using format #fmt + void g(int* p, int fmt) // print *p using format #fmt { // ... uses *p and p[0] only ... } @@ -6529,8 +6532,8 @@ The `unique_ptr` protects against leaks by guaranteeing the deletion of its obje class X { // ... public: - T* p; // bad: it is unclear whether p is owning or not - T* q; // bad: it is unclear whether q is owning or not + T* p; // bad: it is unclear whether p is owning or not + T* q; // bad: it is unclear whether q is owning or not }; We can fix that problem by making ownership explicit: @@ -6539,8 +6542,8 @@ We can fix that problem by making ownership explicit: class X2 { // ... public: - owner p; // OK: p is owning - T* q; // OK: q is not owning + owner p; // OK: p is owning + T* q; // OK: q is not owning }; ##### Note @@ -6569,7 +6572,7 @@ Returning a (raw) pointer imposes a life-time management uncertainty on the call void caller(int n) { - auto p = make_gadget(n); // remember to delete p + auto p = make_gadget(n); // remember to delete p // ... delete p; } @@ -6760,7 +6763,7 @@ The allocation of `buf` may fail and leak the file handle. void f(const string& name) { - ifstream f{name, "r"}; // open the file + ifstream f{name, "r"}; // open the file vector buf(1024); // ... } @@ -6783,7 +6786,7 @@ If you perform two explicit resource allocations in one statement, you could lea This `fun` can be called like this: - fun(shared_ptr(new Widget(a, b)), shared_ptr(new Widget(c, d))); // BAD: potential leak + fun(shared_ptr(new Widget(a, b)), shared_ptr(new Widget(c, d))); // BAD: potential leak This is exception-unsafe because the compiler may reorder the two expressions building the function's two arguments. In particular, the compiler can interleave execution of the two expressions: @@ -6861,9 +6864,9 @@ Consider: void f() { X x; - X* p1 { new X }; // see also ??? - unique_ptr p2 { new X }; // unique ownership; see also ??? - shared_ptr p3 { new X }; // shared ownership; see also ??? + X* p1 { new X }; // see also ??? + unique_ptr p2 { new X }; // unique ownership; see also ??? + shared_ptr p3 { new X }; // shared ownership; see also ??? } This will leak the object used to initialize `p1` (only). @@ -7046,7 +7049,7 @@ Using `unique_ptr` in this way both documents and enforces the function call's o void sink(unique_ptr); // consumes the widget - void sink(widget*); // just uses the widget + void sink(widget*); // just uses the widget ##### Example, bad @@ -7181,7 +7184,7 @@ The following should not pass code review: void my_code() { - f(*g_p); // BAD: passing pointer or reference obtained from a nonlocal smart pointer + f(*g_p); // BAD: passing pointer or reference obtained from a nonlocal smart pointer // that could be inadvertently reset somewhere inside f or it callees g_p->func(); // BAD: same reason, just passing it as a "this" pointer } @@ -7285,7 +7288,7 @@ It is available as part of all C++ Implementations. ##### Example - auto sum = accumulate(begin(a), end(a), 0.0); // good + auto sum = accumulate(begin(a), end(a), 0.0); // good a range version of `accumulate` would be even better: @@ -7293,7 +7296,7 @@ a range version of `accumulate` would be even better: but don't hand-code a well-known algorithm: - int max = v.size(); // bad: verbose, purpose unstated + int max = v.size(); // bad: verbose, purpose unstated double sum = 0.0; for (int i = 0; i < max; ++i) sum = sum + v[i]; @@ -7312,7 +7315,7 @@ A "suitable abstraction" (e.g., library or class) is closer to the application c ##### Example - vector read1(istream& is) // good + vector read1(istream& is) // good { vector res; for (string s; is >> s;) @@ -7322,7 +7325,7 @@ A "suitable abstraction" (e.g., library or class) is closer to the application c The more traditional and lower-level near-equivalent is longer, messier, harder to get right, and most likely slower: - char** read2(istream& is, int maxelem, int maxstring, int* nread) // bad: verbose and incomplete + char** read2(istream& is, int maxelem, int maxstring, int* nread) // bad: verbose and incomplete { auto res = new char*[maxelem]; int elemcount = 0; @@ -7357,7 +7360,7 @@ Readability. Minimize resource retention. Avoid accidental misuse of value. void use() { - int i; // bad: i is needlessly accessible after loop + int i; // bad: i is needlessly accessible after loop for (i = 0; i < 20; ++i) { /* ... */ } // no intended use of i here for (int i = 0; i < 20; ++i) { /* ... */ } // good: i is local to for-loop @@ -7420,11 +7423,11 @@ Readability. Minimize resource retention. for (string s; cin >> s;) v.push_back(s); - for (int i = 0; i < 20; ++i) { // good: i is local to for-loop + for (int i = 0; i < 20; ++i) { // good: i is local to for-loop // ... } - if (auto pc = dynamic_cast(ps)) { // good: pc is local to if-statement + if (auto pc = dynamic_cast(ps)) { // good: pc is local to if-statement // ... deal with Circle ... } else { @@ -7447,7 +7450,7 @@ Readability. Lowering the chance of clashes between unrelated non-local names. Conventional short, local names increase readability: - template // good + template // good void print(ostream& os, const vector& v) { for (int i = 0; i < v.end(); ++i) @@ -7456,7 +7459,7 @@ Conventional short, local names increase readability: An index is conventionally called `i` and there is no hint about the meaning of the vector in this generic function, so `v` is as good name as any. Compare - template // bad: verbose, hard to read + template // bad: verbose, hard to read void print(ostream& target_stream, const vector& current_vector) { for (int current_element_index = 0; @@ -7475,7 +7478,7 @@ Unconventional and short non-local names obscure code: void use1(const string& s) { // ... - tt(s); // bad: what is tt()? + tt(s); // bad: what is tt()? // ... } @@ -7484,7 +7487,7 @@ Better, give non-local entities readable names: void use1(const string& s) { // ... - trim_tail(s); // better + trim_tail(s); // better // ... } @@ -7559,7 +7562,7 @@ One-declaration-per line increases readability and avoid mistake related to the ##### Example, bad - char *p, c, a[7], *pp[7], **aa[10]; // yuck! + char *p, c, a[7], *pp[7], **aa[10]; // yuck! **Exception**: a function declaration can contain several function argument declarations. @@ -7574,7 +7577,7 @@ or better using concepts: ##### Example - double scalbn(double x, int n); // OK: x*pow(FLT_RADIX, n); FLT_RADIX is usually 2 + double scalbn(double x, int n); // OK: x*pow(FLT_RADIX, n); FLT_RADIX is usually 2 or: @@ -7585,7 +7588,7 @@ or: or: - double scalbn(double base, int exponent); // better: base*pow(FLT_RADIX, exponent); FLT_RADIX is usually 2 + double scalbn(double base, int exponent); // better: base*pow(FLT_RADIX, exponent); FLT_RADIX is usually 2 ##### Enforcement @@ -7603,7 +7606,7 @@ Flag non-function arguments with multiple declarators involving declarator opera Consider: - auto p = v.begin(); // vector::iterator + auto p = v.begin(); // vector::iterator auto s = v.size(); auto h = t.future(); auto q = new int[s]; @@ -7614,14 +7617,14 @@ In each case, we save writing a longish, hard-to-remember type that the compiler ##### Example template - auto Container::first() -> Iterator; // Container::Iterator + auto Container::first() -> Iterator; // Container::Iterator **Exception**: Avoid `auto` for initializer lists and in cases where you know exactly which type you want and where an initializer might require conversion. ##### Example - auto lst = { 1, 2, 3 }; // lst is an initializer list (obviously) - auto x{1}; // x is an int (after correction of the C++14 standard; initializer_list in C++11) + auto lst = { 1, 2, 3 }; // lst is an initializer list (obviously) + auto x{1}; // x is an int (after correction of the C++14 standard; initializer_list in C++11) ##### Note @@ -7644,19 +7647,19 @@ Simplify refactoring. ##### Example - void use(int arg) // bad: uninitialized variable + void use(int arg) // bad: uninitialized variable { int i; // ... - i = 7; // initialize i + 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) // OK + void use(int arg) // OK { - int i = 7; // OK: initialized - string s; // OK: default initialized + int i = 7; // OK: initialized + string s; // OK: default initialized // ... } @@ -7736,30 +7739,30 @@ It you are declaring an object that is just about to be initialized from input, However, beware that this may leave uninitialized data beyond the input - and that has been a fertile source of errors and security breaches: constexpr int max = 8*1024; - int buf[max]; // OK, but suspicious: uninitialized + int buf[max]; // OK, but suspicious: uninitialized f.read(buf, max); The cost of initializing that array could be significant in some situations. However, such examples do tend to leave uninitialized variables accessible, so they should be treated with suspicion. constexpr int max = 8*1024; - int buf[max] = {0}; // better in some situations + int buf[max] = {0}; // better in some situations f.read(buf, max); When feasible use a library function that is known not to overflow. For example: - string s; // s is default initialized to "" - cin >> s; // s expands to hold the string + string s; // s is default initialized to "" + cin >> s; // s expands to hold the string Don't consider simple variables that are targets for input operations exceptions to this rule: - int i; // bad + int i; // bad // ... cin >> i; In the not uncommon case where the input target and the input operation get separated (as they should not) the possibility of used-before-set opens up. - int i2 = 0; // better + int i2 = 0; // better // ... cin >> i; @@ -7793,7 +7796,7 @@ Sometimes, a lambda can be used as an initializer to avoid an uninitialized vari error_code ec; Value v = [&] { - auto p = get_value(); // get_value() returns a pair + auto p = get_value(); // get_value() returns a pair ec = p.first; return p.second; }(); @@ -7801,7 +7804,7 @@ Sometimes, a lambda can be used as an initializer to avoid an uninitialized vari or maybe: Value v = [] { - auto p = get_value(); // get_value() returns a pair + auto p = get_value(); // get_value() returns a pair if (p.first) throw Bad_value{p.first}; return p.second; }(); @@ -7845,9 +7848,9 @@ Readability. Limit the scope in which a variable can be used. Don't risk used-be ##### Example, bad - SomeLargeType var; // ugly CaMeLcAsEvArIaBlE + SomeLargeType var; // ugly CaMeLcAsEvArIaBlE - if (cond) // some non-trivial condition + if (cond) // some non-trivial condition Set(&var); else if (cond2 || !cond3) { var = Set2(3.14); @@ -7886,8 +7889,8 @@ The rules for `{}` initialization are simpler, more general, less ambiguous, and For containers, there is a tradition for using `{...}` for a list of elements and `(...)` for sizes: - vector v1(10); // vector of 10 elements with the default value 0 - vector v2 {10}; // vector of 1 element with the value 10 + vector v1(10); // vector of 10 elements with the default value 0 + vector v2 {10}; // vector of 1 element with the value 10 ##### Note @@ -7895,20 +7898,20 @@ For containers, there is a tradition for using `{...}` for a list of elements an ##### Example - int x {7.9}; // error: narrowing - int y = 7.9; // OK: y becomes 7. Hope for a compiler warning + int x {7.9}; // error: narrowing + int y = 7.9; // OK: y becomes 7. Hope for a compiler warning ##### Note `{}` initialization can be used for all initialization; other forms of initialization can't: - auto p = new vector {1, 2, 3, 4, 5}; // initialized vector - D::D(int a, int b) :m{a, b} { // member initializer (e.g., m might be a pair) + auto p = new vector {1, 2, 3, 4, 5}; // initialized vector + D::D(int a, int b) :m{a, b} { // member initializer (e.g., m might be a pair) // ... }; - X var {}; // initialize var to be empty + X var {}; // initialize var to be empty struct S { - int m {7}; // default initializer for a member + int m {7}; // default initializer for a member // ... }; @@ -7926,18 +7929,18 @@ Initialization of a variable declared using `auto` with a single value, e.g., `{ Use `={...}` if you really want an `initializer_list` - auto fib10 = {0, 1, 2, 3, 5, 8, 13, 25, 38, 63}; // fib10 is a list + auto fib10 = {0, 1, 2, 3, 5, 8, 13, 25, 38, 63}; // fib10 is a list ##### Example template void f() { - T x1(1); // T initialized with 1 - T x0(); // bad: function declaration (often a mistake) + T x1(1); // T initialized with 1 + T x0(); // bad: function declaration (often a mistake) - T y1 {1}; // T initialized with 1 - T y0 {}; // default initialized T + T y1 {1}; // T initialized with 1 + T y0 {}; // default initialized T // ... } @@ -7960,8 +7963,8 @@ Using `std::unique_ptr` is the simplest way to avoid leaks. And it is free compa void use(bool leak) { - auto p1 = make_unique(7); // OK - int* p2 = new int{7}; // bad: might leak + auto p1 = make_unique(7); // OK + int* p2 = new int{7}; // bad: might leak // ... if (leak) return; // ... @@ -8026,7 +8029,7 @@ They are not confused with non-standard extensions of built-in arrays. void f() { int a1[n]; - int a2[m]; // error: not ISO C++ + int a2[m]; // error: not ISO C++ // ... } @@ -8063,7 +8066,7 @@ It nicely encapsulates local initialization, including cleaning up scratch varia ##### Example, bad - widget x; // should be const, but: + widget x; // should be const, but: for (auto i = 2; i <= N; ++i) { // this could be some x += some_obj.do_something_with(i); // arbitrarily long code } // needed to initialize x @@ -8082,7 +8085,7 @@ It nicely encapsulates local initialization, including cleaning up scratch varia ##### Example string var = [&]{ - if (!in) return ""; // default + if (!in) return ""; // default string s; for (char c : in >> c) s += toupper(c); @@ -8115,7 +8118,7 @@ Macros complicates tool building. ##### Example, bad - #define Case break; case /* BAD */ + #define Case break; case /* BAD */ This innocuous-looking macro makes a single lower case `c` instead of a `C` into a bad flow-control bug. @@ -8159,9 +8162,9 @@ Convention. Readability. Distinguishing macros. ##### Example - #define forever for(;;) /* very BAD */ + #define forever for(;;) /* very BAD */ - #define FOREVER for(;;) /* Still evil, but at least visible to humans */ + #define FOREVER for(;;) /* Still evil, but at least visible to humans */ ##### Enforcement @@ -8225,9 +8228,9 @@ Statements control the flow of control (except for function calls and exception void use(int n) { - switch (n) { // good - case 0: // ... - case 7: // ... + switch (n) { // good + case 0: // ... + case 7: // ... } } @@ -8253,13 +8256,13 @@ Readability. Error prevention. Efficiency. ##### Example - for (int i = 0; i < v.size(); ++i) // bad + for (int i = 0; i < v.size(); ++i) // bad cout << v[i] << '\n'; - for (auto p = v.begin(); p != v.end(); ++p) // bad + for (auto p = v.begin(); p != v.end(); ++p) // bad cout << *p << '\n'; - for (auto& x : v) // OK + for (auto& x : v) // OK cout << x << '\n'; for (int i = 1; i < v.size(); ++i) // touches two elements: can't be a range-for @@ -8270,7 +8273,7 @@ Readability. Error prevention. Efficiency. for (int i = 0; i < v.size(); ++i) { // body messes with loop variable: can't be a range-for if (i % 2) - ++i; // skip even elements + ++i; // skip even elements else cout << v[i] << '\n'; } @@ -8340,13 +8343,13 @@ Avoid using the loop variable for other purposes after the loop. ##### Example - for (int i = 0; i < 100; ++i) { // GOOD: i var is visible only inside the loop + for (int i = 0; i < 100; ++i) { // GOOD: i var is visible only inside the loop // ... } ##### Example, don't - int j; // BAD: j is visible outside the loop + int j; // BAD: j is visible outside the loop for (j = 0; j < 100; ++j) { // ... } @@ -8505,10 +8508,10 @@ Readability. ##### Example - for (i = 0; i < max; ++i); // BAD: the empty statement is easily overlooked + for (i = 0; i < max; ++i); // BAD: the empty statement is easily overlooked v[i] = f(v[i]); - for (auto x : v) { // better + for (auto x : v) { // better // nothing } @@ -8528,21 +8531,21 @@ Complicated expressions are error-prone. ##### Example - while ((c = getc()) != -1) // bad: assignment hidden in subexpression + while ((c = getc()) != -1) // bad: assignment hidden in subexpression while ((cin >> c1, cin >> c2), c1 == c2) // bad: two non-local variables assigned in a sub-expressions - for (char c1, c2; cin >> c1 >> c2 && c1 == c2;) // better, but possibly still too complicated + for (char c1, c2; cin >> c1 >> c2 && c1 == c2;) // better, but possibly still too complicated - int x = ++i + ++j; // OK: iff i and j are not aliased + int x = ++i + ++j; // OK: iff i and j are not aliased - v[i] = v[j] + v[k]; // OK: iff i != j and i != k + v[i] = v[j] + v[k]; // OK: iff i != j and i != k - x = a + (b = f()) + (c = g()) * 7; // bad: multiple assignments "hidden" in subexpressions + x = a + (b = f()) + (c = g()) * 7; // bad: multiple assignments "hidden" in subexpressions - x = a & b + c * d && e ^ f == 7; // bad: relies on commonly misunderstood precedence rules + x = a & b + c * d && e ^ f == 7; // bad: relies on commonly misunderstood precedence rules - x = x++ + x++ + ++x; // bad: undefined behavior + x = x++ + x++ + ++x; // bad: undefined behavior Some of these expressions are unconditionally bad (e.g., they rely on undefined behavior). Others are simply so complicated and/or unusual that even good programmers could misunderstand them or overlook a problem when in a hurry. @@ -8559,7 +8562,7 @@ A programmer should know and use the basic rules for expressions. if (0 <= x && x < max) // OK - auto t1 = 0 <= x; // bad: unnecessarily verbose + auto t1 = 0 <= x; // bad: unnecessarily verbose auto t2 = x < max; if (t1 && t2) // ... @@ -8633,7 +8636,7 @@ Even if it does something sensible for you, it may do something different on ano ##### Example - v[i] = ++i; // the result is undefined + v[i] = ++i; // the result is undefined A good rule of thumb is that you should not read a value twice in an expression where you write to it. @@ -8666,8 +8669,8 @@ The call will most likely be `f(0, 1)` or `f(1, 0)`, but you don't know which. T ??? overloaded operators can lead to order of evaluation problems (shouldn't :-() - f1()->m(f2()); // m(f1(), f2()) - cout << f1() << f2(); // operator<<(operator<<(cout, f1()), f2()) + f1()->m(f2()); // m(f1(), f2()) + cout << f1() << f2(); // operator<<(operator<<(cout, f1()), f2()) ##### Enforcement @@ -8681,14 +8684,14 @@ Unnamed constants embedded in expressions are easily overlooked and often hard t ##### Example - for (int m = 1; m <= 12; ++m) // don't: magic constant 12 + for (int m = 1; m <= 12; ++m) // don't: magic constant 12 cout << month[m] << '\n'; No, we don't all know that there are 12 months, numbered 1..12, in a year. Better: - constexpr int last_month = 12; // months are numbered 1..12 + constexpr int last_month = 12; // months are numbered 1..12 - for (int m = first_month; m <= last_month; ++m) // better + for (int m = first_month; m <= last_month; ++m) // better cout << month[m] << '\n'; Better still, don't expose constants: @@ -8711,22 +8714,22 @@ A narrowing conversion destroys information, often unexpectedly so. A key example is basic narrowing: double d = 7.9; - int i = d; // bad: narrowing: i becomes 7 + int i = d; // bad: narrowing: i becomes 7 i = (int)d; // bad: we're going to claim this is still not explicit enough void f(int x, long y, double d) { - char c1 = x; // bad: narrowing - char c2 = y; // bad: narrowing - char c3 = d; // bad: narrowing + char c1 = x; // bad: narrowing + char c2 = y; // bad: narrowing + char c3 = d; // bad: narrowing } ##### Note The guideline support library offers a `narrow` operation for specifying that narrowing is acceptable and a `narrow` ("narrow if") that throws an exception if a narrowing would throw away information: - i = narrow_cast(d); // OK (you asked for it): narrowing: i becomes 7 - i = narrow(d); // OK: throws narrowing_error + i = narrow_cast(d); // OK (you asked for it): narrowing: i becomes 7 + i = narrow(d); // OK: throws narrowing_error We also include lossy arithmetic casts, such as from a negative floating point type to an unsigned integral type: @@ -8757,8 +8760,8 @@ Consider: void f(int); void f(char*); - f(0); // call f(int) - f(nullptr); // call f(char*) + f(0); // call f(int) + f(nullptr); // call f(char*) ##### Enforcement @@ -8810,10 +8813,10 @@ The named casts are: * `const_cast` * `reinterpret_cast` * `dynamic_cast` -* `std::move` // `move(x)` is an rvalue reference to `x` -* `std::forward` // `forward(x)` is an rvalue reference to `x` -* `gsl::narrow_cast` // `narrow_cast(x)` is `static_cast(x)` -* `gsl::narrow` // `narrow(x)` is `static_cast(x)` if `static_cast(x) == x` or it throws `narrowing_error` +* `std::move` // `move(x)` is an rvalue reference to `x` +* `std::forward` // `forward(x)` is an rvalue reference to `x` +* `gsl::narrow_cast` // `narrow_cast(x)` is `static_cast(x)` +* `gsl::narrow` // `narrow(x)` is `static_cast(x)` if `static_cast(x) == x` or it throws `narrowing_error` ##### Example @@ -8855,10 +8858,10 @@ Constructs that cannot overflow, don't, and usually runs faster: ##### Example - for (auto& x : v) // print all elements of v + for (auto& x : v) // print all elements of v cout << x << '\n'; - auto p = find(v, x); // find x in v + auto p = find(v, x); // find x in v ##### Enforcement @@ -8878,7 +8881,7 @@ also known as "No naked `new`!" void f(int n) { - auto p = new X[n]; // n default constructed Xs + auto p = new X[n]; // n default constructed Xs // ... delete[] p; } @@ -8901,9 +8904,9 @@ That's what the language requires and mistakes can lead to resource release erro void f(int n) { - auto p = new X[n]; // n default constructed Xs + auto p = new X[n]; // n default constructed Xs // ... - delete p; // error: just delete the object p, rather than delete the array p[] + delete p; // error: just delete the object p, rather than delete the array p[] } ##### Note @@ -9002,7 +9005,7 @@ Incrementing a value beyond a maximum value can lead to memory corruption and un ##### Example, bad int a[10]; - a[10] = 7; // bad + a[10] = 7; // bad int n = 0; while (n++ < 10) @@ -9011,13 +9014,13 @@ Incrementing a value beyond a maximum value can lead to memory corruption and un ##### Example, bad int n = numeric_limits::max(); - int m = n + 1; // bad + int m = n + 1; // bad ##### Example, bad int area(int h, int w) { return h * w; } - auto a = area(10'000'000, 100'000'000); // bad + auto a = area(10'000'000, 100'000'000); // bad **Exception**: Use unsigned types if you really want modulo arithmetic. @@ -9036,7 +9039,7 @@ Decrementing a value beyond a minimum value can lead to memory corruption and un ##### Example, bad int a[10]; - a[-2] = 7; // bad + a[-2] = 7; // bad int n = 101; while (n--) @@ -9490,7 +9493,7 @@ C++ implementations tend to be optimized based on the assumption that exceptions ##### Example, don't - int find_index(vector& vec, const string& x) // don't: exception not used for error handling + int find_index(vector& vec, const string& x) // don't: exception not used for error handling { try { for (int i =0; i < vec.size(); ++i) @@ -9498,7 +9501,7 @@ C++ implementations tend to be optimized based on the assumption that exceptions } catch (int i) { return i; } - return -1; // not found + return -1; // not found } This is more complicated and most likely runs much slower than the obvious alternative. @@ -9539,7 +9542,7 @@ Leaks are typically unacceptable. RAII ("Resource Acquisition Is Initialization" ##### Example - void f1(int i) // Bad: possibly leak + void f1(int i) // Bad: possibly leak { int* p = new int[12]; // ... @@ -9549,7 +9552,7 @@ Leaks are typically unacceptable. RAII ("Resource Acquisition Is Initialization" We could carefully release the resource before the throw: - void f2(int i) // Clumsy: explicit release + void f2(int i) // Clumsy: explicit release { int* p = new int[12]; // ... @@ -9562,7 +9565,7 @@ We could carefully release the resource before the throw: This is verbose. In larger code with multiple possible `throw`s explicit releases become repetitive and error-prone. - void f3(int i) // OK: resource management done by a handle + void f3(int i) // OK: resource management done by a handle { auto p = make_unique(); // ... @@ -9572,21 +9575,21 @@ This is verbose. In larger code with multiple possible `throw`s explicit release Note that this works even when the `throw` is implicit because it happened in a called function: - void f4(int i) // OK: resource management done by a handle + void f4(int i) // OK: resource management done by a handle { auto p = make_unique(); // ... - helper(i); // may throw + helper(i); // may throw // ... } Unless you really need pointer semantics, use a local resource object: - void f5(int i) // OK: resource management done by local object + void f5(int i) // OK: resource management done by local object { vector v(12); // ... - helper(i); // may throw + helper(i); // may throw // ... } @@ -9613,12 +9616,12 @@ One strategy is to add a `valid()` operation to every resource handle: void f() { - vector vs(100); // not std::vector: valid() added + vector vs(100); // not std::vector: valid() added if (!vs.valid()) { // handle error or exit } - Ifstream fs("foo"); // not std::ifstream: valid() added + Ifstream fs("foo"); // not std::ifstream: valid() added if (!fs.valid()) { // handle error or exit } @@ -9690,12 +9693,12 @@ That would be a leak. ##### Example - void leak(int x) // don't: may leak + void leak(int x) // don't: may leak { auto p = new int{7}; if (x < 0) throw Get_me_out_of_here{} // may leak *p // ... - delete p; // we may never get here + delete p; // we may never get here } One way of avoiding such problems is to use resource handles consistently: @@ -9739,14 +9742,14 @@ A user-defined type is unlikely to clash with other people's exceptions. ##### Example, don't - void my_code() // Don't + void my_code() // Don't { // ... - throw 7; // 7 means "moon in the 4th quarter" + throw 7; // 7 means "moon in the 4th quarter" // ... } - void your_code() // Don't + void your_code() // Don't { try { // ... @@ -9764,21 +9767,21 @@ The standard-library classes derived from `exception` should be used only as bas ##### Example, don't - void my_code() // Don't + void my_code() // Don't { // ... throw runtime_error{"moon in the 4th quarter"}; // ... } - void your_code() // Don't + void your_code() // Don't { try { // ... my_code(); // ... } - catch(runtime_error) { // runtime_error means "input buffer too small" + catch(runtime_error) { // runtime_error means "input buffer too small" // ... } } @@ -9801,7 +9804,7 @@ To prevent slicing. try { // ... } - catch (exception e) { // don't: may slice + catch (exception e) { // don't: may slice // ... } @@ -9824,7 +9827,7 @@ We don't know how to write reliable programs if a destructor, a swap, or a memor class Connection { // ... public: - ~Connection() // Don't: very bad destructor + ~Connection() // Don't: very bad destructor { if (cannot_disconnect()) throw I_give_up{information}; // ... @@ -9859,13 +9862,13 @@ Let cleanup actions on the unwinding path be handled by [RAII](#Re-raii). ##### Example, don't - void f() // bad + void f() // bad { try { // ... } catch (...) { - throw; // propagate exception + throw; // propagate exception } } @@ -10201,7 +10204,7 @@ It also avoids brittle or inefficient workarounds. Convention: That's the way th // requires Regular class Vector { // ... - T* elem; // points to sz Ts + T* elem; // points to sz Ts int sz; }; @@ -10212,7 +10215,7 @@ It also avoids brittle or inefficient workarounds. Convention: That's the way th class Container { // ... - void* elem; // points to size elements of some type + void* elem; // points to size elements of some type int sz; }; @@ -10377,7 +10380,7 @@ This `Ordered_container` is quite plausible, but it is very similar to the `Sort Is it better? Is it right? Does it accurately reflect the standard's requirements for `sort`? It is better and simpler just to use `Sortable`: - void sort(Sortable& s); // better + void sort(Sortable& s); // better ##### Note @@ -10403,8 +10406,8 @@ Hard. ##### Example vector v; - auto& x = v.front(); // bad - String& s = v.begin(); // good + auto& x = v.front(); // bad + String& s = v.begin(); // good ##### Enforcement @@ -10420,14 +10423,14 @@ Readability. Direct expression of an idea. To say "`T` is `Sortable`": - template // Correct but verbose: "The parameter is - requires Sortable // of type T which is the name of a type - void sort(T&); // that is Sortable" + template // Correct but verbose: "The parameter is + requires Sortable // of type T which is the name of a type + void sort(T&); // that is Sortable" - template // Better: "The parameter is of type T - void sort(T&); // which is Sortable" + template // Better: "The parameter is of type T + void sort(T&); // which is Sortable" - void sort(Sortable&); // Best: "The parameter is Sortable" + void sort(Sortable&); // Best: "The parameter is Sortable" The shorter versions better match the way we speak. Note that many templates don't need to use the `template` keyword. @@ -10461,11 +10464,11 @@ and should be used only as building blocks for meaningful concepts, rather than int x = 7; int y = 9; - auto z = plus(x, y); // z = 16 + auto z = plus(x, y); // z = 16 string xx = "7"; string yy = "9"; - auto zz = plus(xx, yy); // zz = "79" + auto zz = plus(xx, yy); // zz = "79" Maybe the concatenation was expected. More likely, it was an accident. Defining minus equivalently would give dramatically different sets of accepted types. This `Addable` violates the mathematical rule that addition is supposed to be commutative: `a + b == b + a`. @@ -10491,11 +10494,11 @@ The ability to specify a meaningful semantics is a defining characteristic of a int x = 7; int y = 9; - auto z = plus(x, y); // z = 18 + auto z = plus(x, y); // z = 18 string xx = "7"; string yy = "9"; - auto zz = plus(xx, yy); // error: string is not a Number + auto zz = plus(xx, yy); // error: string is not a Number ##### Note @@ -10514,7 +10517,7 @@ Improves interoperability. Helps implementers and maintainers. ##### Example, bad - template Subtractable = requires(T a, T, b) { a-b; } // correct syntax? + template Subtractable = requires(T a, T, b) { a-b; } // correct syntax? This makes no semantic sense. You need at least `+` to make `-` meaningful and useful. @@ -10652,10 +10655,10 @@ Initially, people will try to define functions with complementary requirements: This is better: - template // general template + template // general template void f(); - template // specialization by concept + template // specialization by concept requires C void f(); @@ -10702,14 +10705,14 @@ In general, passing function objects give better performance than passing pointe ##### Example bool greater(double x, double y) { return x>y; } - sort(v, greater); // pointer to function: potentially slow - sort(v, [](double x, double y) { return x>y; }); // function object - sort(v, greater<>); // function object + sort(v, greater); // pointer to function: potentially slow + sort(v, [](double x, double y) { return x>y; }); // function object + sort(v, greater<>); // function object bool greater_than_7(double x) { return x>7; } - auto x = find_if(v, greater_than_7); // pointer to function: inflexible - auto y = find_if(v, [](double x) { return x>7; }); // function object: carries the needed data - auto y = find_if(v, Greater_than(7)); // function object: carries the needed data + auto x = find_if(v, greater_than_7); // pointer to function: inflexible + auto y = find_if(v, [](double x) { return x>7; }); // function object: carries the needed data + auto y = find_if(v, Greater_than(7)); // function object: carries the needed data ??? these lambdas are crying out for auto parameters -- any objection to making the change? @@ -10787,15 +10790,15 @@ Uniformity: `using` is syntactically similar to `auto`. ##### Example - typedef int (*PFI)(int); // OK, but convoluted + typedef int (*PFI)(int); // OK, but convoluted - using PFI2 = int (*)(int); // OK, preferred + using PFI2 = int (*)(int); // OK, preferred template - typedef int (*PFT)(T); // error + typedef int (*PFT)(T); // error template - using PFT2 = int (*)(T); // OK + using PFT2 = int (*)(T); // OK ##### Enforcement @@ -10809,8 +10812,8 @@ Writing the template argument types explicitly can be tedious and unnecessarily ##### Example - tuple t1 = {1, "Hamlet", 3.14}; // explicit type - auto t2 = make_tuple(1, "Ophelia"s, 3.14); // better; deduced type + tuple t1 = {1, "Hamlet", 3.14}; // explicit type + auto t2 = make_tuple(1, "Ophelia"s, 3.14); // better; deduced type Note the use of the `s` suffix to ensure that the string is a `std::string`, rather than a C-style string. @@ -10940,7 +10943,7 @@ This limits use and typically increases code size. // requires Regular && Allocator class List { public: - struct Link { // does not depend on A + struct Link { // does not depend on A T elem; T* pre; T* suc; @@ -11140,7 +11143,7 @@ Templatizing a class hierarchy that has many functions, especially many virtual ##### Example, bad template - struct Container { // an interface + struct Container { // an interface virtual T* get(int i); virtual T* first(); virtual T* next(); @@ -11183,15 +11186,15 @@ Assume that `Apple` and `Pear` are two kinds of `Fruit`s. void maul(Fruit* p) { - *p = Pear{}; // put a Pear into *p - p[1] = Pear{}; // put a Pear into p[1] + *p = Pear{}; // put a Pear into *p + p[1] = Pear{}; // put a Pear into p[1] } - Apple aa [] = { an_apple, another_apple }; // aa contains Apples (obviously!) + Apple aa [] = { an_apple, another_apple }; // aa contains Apples (obviously!) maul(aa); - Apple& a0 = &aa[0]; // a Pear? - Apple& a1 = &aa[1]; // a Pear? + Apple& a0 = &aa[0]; // a Pear? + Apple& a1 = &aa[1]; // a Pear? Probably, `aa[0]` will be a `Pear` (without the use of a cast!). If `sizeof(Apple) != sizeof(Pear)` the access to `aa[1]` will not be aligned to the proper start of an object in the array. @@ -11204,15 +11207,15 @@ Note that `maul()` violates the a `T*` points to an individual object [Rule](#?? void maul2(Fruit* p) { - *p = Pear{}; // put a Pear into *p + *p = Pear{}; // put a Pear into *p } - vector va = { an_apple, another_apple }; // aa contains Apples (obviously!) + vector va = { an_apple, another_apple }; // aa contains Apples (obviously!) - maul2(va); // error: cannot convert a vector to a Fruit* - maul2(&va[0]); // you asked for it + maul2(va); // error: cannot convert a vector to a Fruit* + maul2(&va[0]); // you asked for it - Apple& a0 = &va[0]; // a Pear? + Apple& a0 = &va[0]; // a Pear? Note that the assignment in `maul2()` violated the no-slicing [Rule](#???). @@ -11247,7 +11250,7 @@ And in general, implementations must deal with dynamic linking. class Shape { // ... template - virtual bool intersect(T* p); // error: template cannot be virtual + virtual bool intersect(T* p); // error: template cannot be virtual }; ##### Note @@ -11272,19 +11275,19 @@ Improve stability of code. Avoids code bloat. It could be a base class: - struct Link_base { // stable + struct Link_base { // stable Link* suc; Link* pre; }; - template // templated wrapper to add type safety + template // templated wrapper to add type safety struct Link : Link_base { T val; }; struct List_base { - Link_base* first; // first element (if any) - int sz; // number of elements + Link_base* first; // first element (if any) + int sz; // number of elements void add_front(Link_base* p); // ... }; @@ -11292,8 +11295,8 @@ It could be a base class: template class List : List_base { public: - void put_front(const T& e) { add_front(new Link{e}); } // implicit cast to Link_base - T& front() { static_cast*>(first).val; } // explicit cast back to Link + void put_front(const T& e) { add_front(new Link{e}); } // implicit cast to Link_base + T& front() { static_cast*>(first).val; } // explicit cast back to Link // ... }; @@ -11470,7 +11473,7 @@ Often a `constexpr` function implies less compile-time overhead than alternative template // requires Number - constexpr T pow(T v, int n) // power/exponential + constexpr T pow(T v, int n) // power/exponential { T res = 1; while (n--) res *= v; @@ -11581,11 +11584,11 @@ Generality. Reusability. Don't gratuitously commit to details; use the most gene Use `!=` instead of `<` to compare iterators; `!=` works for more objects because it doesn't rely on ordering. - for (auto i = first; i < last; ++i) { // less generic + for (auto i = first; i < last; ++i) { // less generic // ... } - for (auto i = first; i != last; ++i) { // good; more generic + for (auto i = first; i != last; ++i) { // good; more generic // ... } @@ -11668,8 +11671,8 @@ It provides better support for high-level programming and often generates faster char ch = 7; void* pv = &ch; - int* pi = pv; // not C++ - *pi = 999; // overwrite sizeof(int) bytes near &ch + int* pi = pv; // not C++ + *pi = 999; // overwrite sizeof(int) bytes near &ch ##### Enforcement @@ -11727,7 +11730,7 @@ You can call C++ from C: // in C++: extern "C" X call_f(Y* p, int i) { - return p->f(i); // possibly a virtual function call + return p->f(i); // possibly a virtual function call } ##### Enforcement @@ -11769,11 +11772,11 @@ Examples are `.hh` and `.cxx`. Use such names equivalently. ##### Example // foo.h: - extern int a; // a declaration + extern int a; // a declaration extern void foo(); // foo.cpp: - int a; // a definition + int a; // a definition void foo() { ++a; } `foo.h` provides the interface to `foo.cpp`. Global variables are best avoided. @@ -11781,7 +11784,7 @@ Examples are `.hh` and `.cxx`. Use such names equivalently. ##### Example, bad // foo.h: - int a; // a definition + int a; // a definition void foo() { ++a; } `#include` twice in a program and you get a linker error for two one-definition-rule violations. @@ -11905,7 +11908,7 @@ The errors will not be caught until link time for a program calling `bar` or `fo void foo(int) { /* ... */ } int bar(double) { /* ... */ } - double foobar(int); // error: wrong return type + double foobar(int); // error: wrong return type The return-type error for `foobar` is now caught immediately when `foo.cpp` is compiled. The argument-type error for `bar` cannot be caught until link time because of the possibility of overloading, but systematic use of `.h` files increases the likelihood that it is caught earlier by the programmer. @@ -12697,7 +12700,7 @@ Pointers should not be used as arrays. `span` is a bounds-checked, safe alternat int a[5]; span av = a; - g(a.data(), a.length()); // OK, if you have no choice + g(a.data(), a.length()); // OK, if you have no choice g1(a); // OK - no decay here, instead use implicit span ctor } @@ -12716,7 +12719,7 @@ These functions all have bounds-safe overloads that take `span`. Standard types void f() { array a, b; - memset(a.data(), 0, 10); // BAD, and contains a length error (length = 10 * sizeof(int)) + memset(a.data(), 0, 10); // BAD, and contains a length error (length = 10 * sizeof(int)) memcmp(a.data(), b.data(), 10); // BAD, and contains a length error (length = 10 * sizeof(int)) } @@ -12784,16 +12787,16 @@ References are never owners. The names are mostly ISO standard-library style (lower case and underscore): -* `T*` // The `T*` is not an owner, may be null; assumed to be pointing to a single element. -* `char*` // A C-style string (a zero-terminated array of characters); may be null. -* `const char*` // A C-style string; may be null. -* `T&` // The `T&` is not an owner and can never be a "null reference"; references are always bound to objects. +* `T*` // The `T*` is not an owner, may be null; assumed to be pointing to a single element. +* `char*` // A C-style string (a zero-terminated array of characters); may be null. +* `const char*` // A C-style string; may be null. +* `T&` // The `T&` is not an owner and can never be a "null reference"; references are always bound to objects. The "raw-pointer" notation (e.g. `int*`) is assumed to have its most common meaning; that is, a pointer points to an object, but does not own it. Owners should be converted to resource handles (e.g., `unique_ptr` or `vector`) or marked `owner` -* `owner` // a `T*`that owns the object pointed/referred to; may be `nullptr`. -* `owner` // a `T&` that owns the object pointed/referred to. +* `owner` // a `T*`that owns the object pointed/referred to; may be `nullptr`. +* `owner` // a `T&` that owns the object pointed/referred to. `owner` is used to mark owning pointers in code that cannot be upgraded to use proper resource handles. Reasons for that include: @@ -12808,13 +12811,13 @@ An `owner` is assumed to refer to an object on the free store (heap). If something is not supposed to be `nullptr`, say so: -* `not_null` // `T` is usually a pointer type (e.g., `not_null` and `not_null>`) that may not be `nullptr`. +* `not_null` // `T` is usually a pointer type (e.g., `not_null` and `not_null>`) that may not be `nullptr`. `T` can be any type for which `==nullptr` is meaningful. -* `span` // [`p`:`p+n`), constructor from `{p, q}` and `{p, n}`; `T` is the pointer type -* `span_p` // `{p, predicate}` [`p`:`q`) where `q` is the first element for which `predicate(*p)` is true -* `string_span` // `span` -* `cstring_span` // `span` +* `span` // [`p`:`p+n`), constructor from `{p, q}` and `{p, n}`; `T` is the pointer type +* `span_p` // `{p, predicate}` [`p`:`q`) where `q` is the first element for which `predicate(*p)` is true +* `string_span` // `span` +* `cstring_span` // `span` A `span` refers to zero or more mutable `T`s unless `T` is a `const` type. @@ -12822,8 +12825,8 @@ A `span` refers to zero or more mutable `T`s unless `T` is a `const` type. A `char*` that points to something that is not a C-style string (e.g., a pointer into an input buffer) should be represented by a `span`. There is no really good way to say "pointer to a single `char`" (`string_span{p, 1}` can do that, and `T*` where `T` is a `char` in a template that has not been specialized for C-style strings). -* `zstring` // a `char*` supposed to be a C-style string; that is, a zero-terminated sequence of `char` or `null_ptr` -* `czstring` // a `const char*` supposed to be a C-style string; that is, a zero-terminated sequence of `const` `char` or `null_ptr` +* `zstring` // a `char*` supposed to be a C-style string; that is, a zero-terminated sequence of `char` or `null_ptr` +* `czstring` // a `const char*` supposed to be a C-style string; that is, a zero-terminated sequence of `const` `char` or `null_ptr` Logically, those last two aliases are not needed, but we are not always logical, and they make the distinction between a pointer to one `char` and a pointer to a C-style string explicit. A sequence of characters that is not assumed to be zero-terminated should be a `char*`, rather than a `zstring`. @@ -12844,17 +12847,17 @@ Use `not_null` for C-style strings that cannot be `nullptr`. ??? Do we * `Expects` // precondition assertion. Currently placed in function bodies. Later, should be moved to declarations. // `Expects(p)` terminates the program unless `p == true` // `Expect` in under control of some options (enforcement, error message, alternatives to terminate) -* `Ensures` // postcondition assertion. Currently placed in function bodies. Later, should be moved to declarations. +* `Ensures` // postcondition assertion. Currently placed in function bodies. Later, should be moved to declarations. These assertions is currently macros (yuck!) pending standard commission decisions on contracts and assertion syntax. ## GSL.util: Utilities -* `finally` // `finally(f)` makes a `final_action{f}` with a destructor that invokes `f` -* `narrow_cast` // `narrow_cast(x)` is `static_cast(x)` -* `narrow` // `narrow(x)` is `static_cast(x)` if `static_cast(x) == x` or it throws `narrowing_error` -* `[[implicit]]` // "Marker" to put on single-argument constructors to explicitly make them non-explicit. -* `move_owner` // `p = move_owner(q)` means `p = q` but ??? +* `finally` // `finally(f)` makes a `final_action{f}` with a destructor that invokes `f` +* `narrow_cast` // `narrow_cast(x)` is `static_cast(x)` +* `narrow` // `narrow(x)` is `static_cast(x)` if `static_cast(x) == x` or it throws `narrowing_error` +* `[[implicit]]` // "Marker" to put on single-argument constructors to explicitly make them non-explicit. +* `move_owner` // `p = move_owner(q)` means `p = q` but ??? ## GSL.concept: Concepts @@ -12863,13 +12866,13 @@ They are likely to be very similar to what will become part of the ISO C++ stand The notation is that of the ISO WG21 Concepts TS (???ref???). * `Range` -* `String` // ??? -* `Number` // ??? +* `String` // ??? +* `Number` // ??? * `Sortable` * `Pointer` // A type with `*`, `->`, `==`, and default construction (default construction is assumed to set the singular "null" value) [see smartptrconcepts](#Rr-smartptrconcepts) * `Unique_ptr` // A type that matches `Pointer`, has move (not copy), and matches the Lifetime profile criteria for a `unique` owner type [see smartptrconcepts](#Rr-smartptrconcepts) * `Shared_ptr` // A type that matches `Pointer`, has copy, and matches the Lifetime profile criteria for a `shared` owner type [see smartptrconcepts](#Rr-smartptrconcepts) -* `EqualityComparable` // ???Must we suffer CaMelcAse??? +* `EqualityComparable` // ???Must we suffer CaMelcAse??? * `Convertible` * `Common` * `Boolean` @@ -12929,7 +12932,7 @@ Comments are not updated as consistently as code. ##### Example, bad - auto x = m*v1 + vv; // multiply m with v1 and add the result to vv + auto x = m*v1 + vv; // multiply m with v1 and add the result to vv ##### Enforcement @@ -13006,7 +13009,7 @@ This is not evil. Some styles distinguishes types from non-types. template - class Hash_tbl { // maps string to T + class Hash_tbl { // maps string to T // ... }; @@ -13261,9 +13264,9 @@ The use in expressions argument doesn't hold for references. ##### Example - T& operator[](size_t); // OK - T &operator[](size_t); // just strange - T & operator[](size_t); // undecided + T& operator[](size_t); // OK + T &operator[](size_t); // just strange + T & operator[](size_t); // undecided ##### Enforcement @@ -13809,7 +13812,7 @@ Prevent leaks. Leaks can lead to performance degradation, mysterious error, syst class Vector { // ... private: - T* elem; // sz elements on the free store, owned by the class object + T* elem; // sz elements on the free store, owned by the class object int sz; }; @@ -13886,18 +13889,18 @@ To avoid extremely hard-to-find errors. Dereferencing such a pointer is undefine ##### Example - string* bad() // really bad + string* bad() // really bad { vector v = { "this", "will", "cause" "trouble" }; - return &v[0]; // leaking a pointer into a destroyed member of a destroyed object (v) + return &v[0]; // leaking a pointer into a destroyed member of a destroyed object (v) } void use() { string* p = bad(); vector xx = {7, 8, 9}; - string x = *p; // undefined behavior: x may not be 1 - *p = "Evil!"; // undefined behavior: we don't know what (if anything) is allocated a location p + string x = *p; // undefined behavior: x may not be 1 + *p = "Evil!"; // undefined behavior: we don't know what (if anything) is allocated a location p } The `string`s of `v` are destroyed upon exit from `bad()` and so is `v` itself. The returned pointer points to unallocated memory on the free store. This memory (pointed into by `p`) may have been reallocated by the time `*p` is executed. There may be no `string` to read and a write through `p` could easily corrupt objects of unrelated types. @@ -13916,7 +13919,7 @@ To provide statically type-safe manipulation of elements. template class Vector { // ... - T* elem; // point to sz elements of type T + T* elem; // point to sz elements of type T int sz; };