update from master

This commit is contained in:
Sergey Zubkov
2021-06-20 13:25:01 -04:00
parent e3d16e5f17
commit 79094ad1e7

View File

@@ -1,6 +1,6 @@
# <a name="main"></a>C++ Core Guidelines
March 11, 2021
June 17, 2021
Editors:
@@ -551,7 +551,7 @@ A better approach is to be explicit about the meaning of the double (new speed o
change_speed(Speed s); // better: the meaning of s is specified
// ...
change_speed(2.3); // error: no unit
change_speed(23m / 10s); // meters per second
change_speed(23_m / 10s); // meters per second
We could have accepted a plain (unit-less) `double` as a delta, but that would have been error-prone.
If we wanted both absolute speed and deltas, we would have defined a `Delta` type.
@@ -1758,7 +1758,7 @@ Make the interface precisely specified and compile-time checkable in the (not so
Use the C++20 style of requirements specification. For example:
template<typename Iter, typename Val>
// requires InputIterator<Iter> && EqualityComparable<ValueType<Iter>>, Val>
// requires InputIterator<Iter> && EqualityComparable<ValueType<Iter>, Val>
Iter find(Iter first, Iter last, Val v)
{
// ...
@@ -2389,6 +2389,7 @@ Other function rules:
* [F.53: Avoid capturing by reference in lambdas that will be used non-locally, including returned, stored on the heap, or passed to another thread](#Rf-value-capture)
* [F.54: If you capture `this`, capture all variables explicitly (no default capture)](#Rf-this-capture)
* [F.55: Don't use `va_arg` arguments](#F-varargs)
* [F.56: Avoid unnecessary condition nesting](#F-nesting)
Functions have strong similarities to lambdas and function objects.
@@ -3086,7 +3087,7 @@ Such older advice is now obsolete; it does not add value, and it interferes with
}
The argument for adding `const` to a return value is that it prevents (very rare) accidental access to a temporary.
The argument against is prevents (very frequent) use of move semantics.
The argument against is that it prevents (very frequent) use of move semantics.
##### Exceptions
@@ -3894,7 +3895,7 @@ Unfortunately, there is no simple way to capture by reference to `const` to get
##### Example
Here, a large object (a network message) is passed to an iterative algorithm, and is it not efficient or correct to copy the message (which might not be copyable):
Here, a large object (a network message) is passed to an iterative algorithm, and it is not efficient or correct to copy the message (which might not be copyable):
std::for_each(begin(sockets), end(sockets), [&message](auto& socket)
{
@@ -4034,6 +4035,73 @@ Declaring a `...` parameter is sometimes useful for techniques that don't involv
* Issue a diagnostic for using `va_list`, `va_start`, or `va_arg`.
* Issue a diagnostic for passing an argument to a vararg parameter of a function that does not offer an overload for a more specific type in the position of the vararg. To fix: Use a different function, or `[[suppress(types)]]`.
### <a name="F-nesting"></a>F.56: Avoid unnecessary condition nesting
##### Reason
Shallow nesting of conditions makes the code easier to follow. It also makes the intent clearer.
Strive to place the essential code at outermost scope, unless this obscures intent.
##### Example
Use a guard-clause to take care of exceptional cases and return early.
// Bad: Deep nesting
void foo() {
...
if (x) {
computeImportantThings(x);
}
}
// Bad: Still a redundant else.
void foo() {
...
if (!x) {
return;
}
else {
computeImportantThings(x);
}
}
// Good: Early return, no redundant else
void foo() {
...
if (!x)
return;
computeImportantThings(x);
}
##### Example
// Bad: Unnecessary nesting of conditions
void foo() {
...
if (x) {
if (y) {
computeImportantThings(x);
}
}
}
// Good: Merge conditions + return early
void foo() {
...
if (!(x && y))
return;
computeImportantThings(x);
}
##### Enforcement
Flag a redundant `else`.
Flag a functions whose body is simply a conditional statement enclosing a block.
# <a name="S-class"></a>C: Classes and class hierarchies
A class is a user-defined type, for which a programmer can define the representation, operations, and interfaces.
@@ -4420,6 +4488,7 @@ Concrete type rule summary:
* [C.10: Prefer concrete types over class hierarchies](#Rc-concrete)
* [C.11: Make concrete types regular](#Rc-regular)
* [C.12: Don't make data members `const` or references](#Rc-constref)
### <a name="Rc-concrete"></a>C.10: Prefer concrete types over class hierarchies
@@ -4509,6 +4578,33 @@ Often, such types are referred to as "move-only types".
???
### <a name="Rc-constref"></a>C.12: Don't make data members `const` or references
##### Reason
They are not useful, and make types difficult to use by making them either uncopyable or partially uncopyable for subtle reasons.
##### Example; bad
class bad {
const int i; // bad
string& s; // bad
// ...
};
The `const` and `&` data members make this class "only-sort-of-copyable" -- copy-constructible but not copy-assignable.
##### Note
If you need a member to point to something, use a pointer (raw or smart, and `gsl::not_null` if it should not be null) instead of a reference.
##### Enforcement
Flag a data member that is `const`, `&`, or `&&`.
## <a name="S-ctor"></a>C.ctor: Constructors, assignments, and destructors
These functions control the lifecycle of objects: creation, copy, move, and destruction.
@@ -4569,7 +4665,7 @@ Copy and move rules:
* [C.64: A move operation should move and leave its source in a valid state](#Rc-move-semantic)
* [C.65: Make move assignment safe for self-assignment](#Rc-move-self)
* [C.66: Make move operations `noexcept`](#Rc-move-noexcept)
* [C.67: A polymorphic class should suppress copying](#Rc-copy-virtual)
* [C.67: A polymorphic class should suppress public copy/move](#Rc-copy-virtual)
Other default operations rules:
@@ -4655,7 +4751,7 @@ into more expensive copies, or making a class move-only.
// ...
}
Given that "special attention" was needed for the destructor (here, to deallocate), the likelihood that copy and move assignment (both will implicitly destroy an object) are correct is low (here, we would get double deletion).
Given that "special attention" was needed for the destructor (here, to deallocate), the likelihood that the implicitly-defined copy and move assignment operators will be correct is low (here, we would get double deletion).
##### Note
@@ -4678,12 +4774,13 @@ defined as defaulted.
};
To prevent slicing as per [C.67](#Rc-copy-virtual),
`=delete` the copy and move operations and add a `clone`:
make the copy and move operations protected or `=delete`d, and add a `clone`:
class ClonableBase {
public:
virtual unique_ptr<ClonableBase> clone() const;
virtual ~ClonableBase() = default;
CloneableBase() = default;
ClonableBase(const ClonableBase&) = delete;
ClonableBase& operator=(const ClonableBase&) = delete;
ClonableBase(ClonableBase&&) = delete;
@@ -4853,7 +4950,7 @@ For resources represented as classes with a complete set of default operations,
##### Note
What about a sockets that won't close? A destructor, close, or cleanup operation [should never fail](#Rc-dtor-fail).
What about a socket that won't close? A destructor, close, or cleanup operation [should never fail](#Rc-dtor-fail).
If it does nevertheless, we have a problem that has no really good solution.
For starters, the writer of a destructor does not know why the destructor is called and cannot "refuse to act" by throwing an exception.
See [discussion](#Sd-never-fail).
@@ -6032,7 +6129,7 @@ Consider:
(Simple) Assignment operators should not contain the pattern `if (this == &a) return *this;` ???
### <a name="Rc-move-assignment"></a>C.63: Make move assignment non-`virtual`, take the parameter by `&&`, and return by non-`const &`
### <a name="Rc-move-assignment"></a>C.63: Make move assignment non-`virtual`, take the parameter by `&&`, and return by non-`const&`
##### Reason
@@ -6156,7 +6253,7 @@ Here is a way to move a pointer without a test (imagine it as code in the implem
##### Reason
A throwing move violates most people's reasonably assumptions.
A throwing move violates most people's reasonable assumptions.
A non-throwing move will be used more efficiently by standard-library and language facilities.
##### Example
@@ -6193,12 +6290,14 @@ This `Vector2` is not just inefficient, but since a vector copy requires allocat
(Simple) A move operation should be marked `noexcept`.
### <a name="Rc-copy-virtual"></a>C.67: A polymorphic class should suppress copying
### <a name="Rc-copy-virtual"></a>C.67: A polymorphic class should suppress public copy/move
##### Reason
A *polymorphic class* is a class that defines or inherits at least one virtual function. It is likely that it will be used as a base class for other derived classes with polymorphic behavior. If it is accidentally passed by value, with the implicitly generated copy constructor and assignment, we risk slicing: only the base portion of a derived object will be copied, and the polymorphic behavior will be corrupted.
If the class has no data, `=delete` the copy/move functions. Otherwise, make them protected.
##### Example, bad
class B { // BAD: polymorphic base class doesn't suppress copying
@@ -6225,6 +6324,7 @@ A *polymorphic class* is a class that defines or inherits at least one virtual f
class B { // GOOD: polymorphic class suppresses copying
public:
B() = default;
B(const B&) = delete;
B& operator=(const B&) = delete;
virtual char m() { return 'B'; }
@@ -6255,13 +6355,13 @@ Classes that represent exception objects need both to be polymorphic and copy-co
##### Enforcement
* Flag a polymorphic class with a non-deleted copy operation.
* Flag a polymorphic class with a public copy operation.
* Flag an assignment of polymorphic class objects.
## C.other: Other default operation rules
In addition to the operations for which the language offer default implementations,
there are a few operations that are so foundational that it rules for their definition are needed:
In addition to the operations for which the language offers default implementations,
there are a few operations that are so foundational that specific rules for their definition are needed:
comparisons, `swap`, and `hash`.
### <a name="Rc-eqdefault"></a>C.80: Use `=default` if you have to be explicit about using the default semantics
@@ -6480,7 +6580,7 @@ If a `swap` tries to exit with an exception, it's a bad design error and the pro
##### Reason
Asymmetric treatment of operands is surprising and a source of errors where conversions are possible.
`==` is a fundamental operations and programmers should be able to use it without fear of failure.
`==` is a fundamental operation and programmers should be able to use it without fear of failure.
##### Example
@@ -6562,11 +6662,11 @@ Of course there are ways of making `==` work in a hierarchy, but the naive appro
##### Note
This rule applies to all the usual comparison operators: `!=`, `<`, `<=`, `>`, and `>=`.
This rule applies to all the usual comparison operators: `!=`, `<`, `<=`, `>`, `>=`, and `<=>`.
##### Enforcement
* Flag a virtual `operator==()`; same for other comparison operators: `!=`, `<`, `<=`, `>`, and `>=`.
* Flag a virtual `operator==()`; same for other comparison operators: `!=`, `<`, `<=`, `>`, `>=`, and `<=>`.
### <a name="Rc-hash"></a>C.89: Make a `hash` `noexcept`
@@ -6845,11 +6945,11 @@ Class hierarchy rule summary:
Designing rules for classes in a hierarchy summary:
* [C.126: An abstract class typically doesn't need a constructor](#Rh-abstract-ctor)
* [C.126: An abstract class typically doesn't need a user-written constructor](#Rh-abstract-ctor)
* [C.127: A class with a virtual function should have a virtual or protected destructor](#Rh-dtor)
* [C.128: Virtual functions should specify exactly one of `virtual`, `override`, or `final`](#Rh-override)
* [C.129: When designing a class hierarchy, distinguish between implementation inheritance and interface inheritance](#Rh-kind)
* [C.130: For making deep copies of polymorphic classes prefer a virtual `clone` function instead of copy construction/assignment](#Rh-copy)
* [C.130: For making deep copies of polymorphic classes prefer a virtual `clone` function instead of public copy construction/assignment](#Rh-copy)
* [C.131: Avoid trivial getters and setters](#Rh-get)
* [C.132: Don't make a function `virtual` without reason](#Rh-virtual)
* [C.133: Avoid `protected` data](#Rh-protected)
@@ -7018,7 +7118,7 @@ Furthermore, we can update `D1` and `D2` in ways that are not binary compatible
## C.hierclass: Designing classes in a hierarchy:
### <a name="Rh-abstract-ctor"></a>C.126: An abstract class typically doesn't need a constructor
### <a name="Rh-abstract-ctor"></a>C.126: An abstract class typically doesn't need a user-written constructor
##### Reason
@@ -7026,7 +7126,20 @@ An abstract class typically does not have any data for a constructor to initiali
##### Example
???
class Shape {
public:
// no user-written constructor needed in abstract base class
virtual Point center() const = 0; // pure virtual
virtual void move(Point to) = 0;
// ... more pure virtual functions...
virtual ~Shape() {} // destructor
};
class Circle : public Shape {
public:
Circle(Point p, int rad); // constructor in derived class
Point center() const override { return x; }
};
##### Exception
@@ -7054,6 +7167,7 @@ A class with a virtual function is usually (and in general) used via a pointer t
// bad: derived from a class without a virtual destructor
struct D : B {
string s {"default"};
// ...
};
void use()
@@ -7359,7 +7473,7 @@ at the cost of the functionality being available only to users of the hierarchy.
* ???
### <a name="Rh-copy"></a>C.130: For making deep copies of polymorphic classes prefer a virtual `clone` function instead of copy construction/assignment
### <a name="Rh-copy"></a>C.130: For making deep copies of polymorphic classes prefer a virtual `clone` function instead of public copy construction/assignment
##### Reason
@@ -7370,8 +7484,8 @@ Copying a polymorphic class is discouraged due to the slicing problem, see [C.67
class B {
public:
virtual owner<B*> clone() = 0;
B() = default;
virtual ~B() = default;
B(const B&) = delete;
B& operator=(const B&) = delete;
};
@@ -8042,7 +8156,7 @@ Subscripting the resulting base pointer will lead to invalid object access and p
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
p[1].x = 7; // overwrite a[0].y
use(a); // bad: a decays to &a[0] which is converted to a B*
@@ -8235,7 +8349,7 @@ The string returned by `ff()` is destroyed before the returned pointer into it c
##### Enforcement
Flag all conversion operators.
Flag all non-explicit conversion operators.
### <a name="Ro-custom"></a>C.165: Use `using` for customization points
@@ -8289,7 +8403,7 @@ The problem is that the unqualified and qualified lookups both have uses.
##### Reason
The `&` operator is fundamental in C++.
Many parts of the C++ semantics assumes its default meaning.
Many parts of the C++ semantics assume its default meaning.
##### Example
@@ -8344,9 +8458,9 @@ By itself, `cout_my_class` would be OK, but it is not usable/composable with cod
##### Note
There are strong and vigorous conventions for the meaning most operators, such as
There are strong and vigorous conventions for the meaning of most operators, such as
* comparisons (`==`, `!=`, `<`, `<=`, `>`, and `>=`),
* comparisons (`==`, `!=`, `<`, `<=`, `>`, `>=`, and `<=>`),
* arithmetic operations (`+`, `-`, `*`, `/`, and `%`)
* access operations (`.`, `->`, unary `*`, and `[]`)
* assignment (`=`)
@@ -8705,8 +8819,8 @@ If you wanted to see the bytes of an `int`, use a (named) cast:
// ...
}
Accessing the result of an `reinterpret_cast` to a different type from the objects declared type is defined behavior (even though `reinterpret_cast` is discouraged),
but at least we can see that something tricky is going on.
Accessing the result of a `reinterpret_cast` to a type different from the object's declared type is defined behavior. (Using `reinterpret_cast` is discouraged,
but at least we can see that something tricky is going on.)
##### Note
@@ -8807,7 +8921,7 @@ Such off-by-one `switch`-statements are often the results of an added enumerator
##### Enforcement
* Flag `switch`-statements where the `case`s cover most but not all enumerators of an enumeration.
* Flag `switch`-statements where the `case`s cover a few enumerators of an enumeration, but has no `default`.
* Flag `switch`-statements where the `case`s cover a few enumerators of an enumeration, but there is no `default`.
### <a name="Renum-class"></a>Enum.3: Prefer class enums over "plain" enums
@@ -10059,7 +10173,7 @@ Readability. Minimize resource retention. Avoid accidental misuse of value.
// ... 200 lines of code without intended use of fn or is ...
}
This function is by most measure too long anyway, but the point is that the resources used by `fn` and the file handle held by `is`
This function is by most measures too long anyway, but the point is that the resources used by `fn` and the file handle held by `is`
are retained for much longer than needed and that unanticipated use of `is` and `fn` could happen later in the function.
In this case, it might be a good idea to factor out the read:
@@ -10522,7 +10636,7 @@ Assuming that there is a logical connection between `i` and `j`, that connection
pair<widget, widget> make_related_widgets(bool x)
{
return (x) ? {f1(), f2()} : {f3(), f4() };
return (x) ? {f1(), f2()} : {f3(), f4()};
}
auto [i, j] = make_related_widgets(cond); // C++17
@@ -11518,9 +11632,8 @@ C++17 tightens up the rules for the order of evaluation, but the order of evalua
int i = 0;
f(++i, ++i);
The call will most likely be `f(0, 1)` or `f(1, 0)`, but you don't know which.
Technically, the behavior is undefined.
In C++17, this code does not have undefined behavior, but it is still not specified which argument is evaluated first.
Before C++17, the behavior is undefined, so the behavior could be anything (e.g., `f(2, 2)`).
Since C++17, this code does not have undefined behavior, but it is still not specified which argument is evaluated first. The call will be `f(1, 2)` or `f(2, 1)`, but you don't know which.
##### Example
@@ -11589,7 +11702,7 @@ A key example is basic narrowing:
##### Note
The guidelines support library offers a `narrow_cast` operation for specifying that narrowing is acceptable and a `narrow` ("narrow if") that throws an exception if a narrowing would throw away information:
The guidelines support library offers a `narrow_cast` operation for specifying that narrowing is acceptable and a `narrow` ("narrow if") that throws an exception if a narrowing would throw away legal values:
i = narrow_cast<int>(d); // OK (you asked for it): narrowing: i becomes 7
i = narrow<int>(d); // OK: throws narrowing_error
@@ -12489,7 +12602,7 @@ Readability. Error prevention. Efficiency.
##### Example
for (gsl::index i = 0; i < v.size(); ++i) // bad
cout << v[i] << '\n';
cout << v[i] << '\n';
for (auto p = v.begin(); p != v.end(); ++p) // bad
cout << *p << '\n';
@@ -12504,10 +12617,8 @@ Readability. Error prevention. Efficiency.
cout << f(v, &v[i]) << '\n';
for (gsl::index i = 0; i < v.size(); ++i) { // body messes with loop variable: can't be a range-for
if (i % 2 == 0)
continue; // skip even elements
else
cout << v[i] << '\n';
if (i % 2 != 0)
cout << v[i] << '\n'; // output odd elements
}
A human or a good static analyzer might determine that there really isn't a side effect on `v` in `f(v, &v[i])` so that the loop can be rewritten.
@@ -13573,7 +13684,7 @@ This implies added work for the programmer, is error-prone, and deprives the com
// address data using the order defined by compare_doubles
qsort(data, 100, sizeof(double), compare_doubles);
From the point of view of interface design is that `qsort` throws away useful information.
From the point of view of interface design, `qsort` throws away useful information.
We can do better (in C++98)
@@ -13786,7 +13897,7 @@ There are similar techniques for selecting the optimal function to call.
##### Note
The ideal is {not} to try execute everything at compile time.
The ideal is *not* to try execute everything at compile time.
Obviously, most computations depend on inputs so they can't be moved to compile time,
but beyond that logical constraint is the fact that complex compile-time computation can seriously increase compile times
and complicate debugging.
@@ -14089,14 +14200,14 @@ The less sharing you do, the less chance you have to wait on a lock (so performa
##### Example
bool validate(const vector<Reading>&);
Graph<Temp_node> temperature_gradiants(const vector<Reading>&);
Graph<Temp_node> temperature_gradients(const vector<Reading>&);
Image altitude_map(const vector<Reading>&);
// ...
void process_readings(const vector<Reading>& surface_readings)
{
auto h1 = async([&] { if (!validate(surface_readings)) throw Invalid_data{}; });
auto h2 = async([&] { return temperature_gradiants(surface_readings); });
auto h2 = async([&] { return temperature_gradients(surface_readings); });
auto h3 = async([&] { return altitude_map(surface_readings); });
// ...
h1.get();
@@ -14142,7 +14253,7 @@ Application concepts are easier to reason about.
With the exception of `async()`, the standard-library facilities are low-level, machine-oriented, threads-and-lock level.
This is a necessary foundation, but we have to try to raise the level of abstraction: for productivity, for reliability, and for performance.
This is a potent argument for using higher level, more applications-oriented libraries (if possibly, built on top of standard-library facilities).
This is a potent argument for using higher level, more applications-oriented libraries (if possible, built on top of standard-library facilities).
##### Enforcement
@@ -14205,7 +14316,7 @@ Use a `mutex` for more complicated examples.
Experience shows that concurrent code is exceptionally hard to get right
and that compile-time checking, run-time checks, and testing are less effective at finding concurrency errors
than they are at finding errors in sequential code.
Subtle concurrency errors can have dramatically bad effects, including memory corruption and deadlocks.
Subtle concurrency errors can have dramatically bad effects, including memory corruption, deadlocks, and security vulnerabilities.
##### Example
@@ -14230,7 +14341,7 @@ cases that should have been caught but were allowed.
is a powerful example of dynamic tools: it changes the build and execution of your program to add bookkeeping on memory access,
absolutely identifying data races in a given execution of your binary.
The cost for this is both memory (5-10x in most cases) and CPU slowdown (2-20x).
Dynamic tools like this are best when applied to integration tests, canary pushes, or unittests that operate on multiple threads.
Dynamic tools like this are best when applied to integration tests, canary pushes, or unit tests that operate on multiple threads.
Workload matters: When TSAN identifies a problem, it is effectively always an actual data race,
but it can only identify races seen in a given execution.
@@ -14347,6 +14458,8 @@ Here, the writers of `thread1` and `thread2` are still not agreeing on the order
In real code, `mutex`es are rarely named to conveniently remind the programmer of an intended relation and intended order of acquisition.
In real code, `mutex`es are not always conveniently acquired on consecutive lines.
##### Note
In C++17 it's possible to write plain
lock_guard lck1(m1, adopt_lock);
@@ -14614,7 +14727,7 @@ Flag `detach()`.
##### Reason
Copying a small amount of data is cheaper to copy and access than to share it using some locking mechanism.
A small amount of data is cheaper to copy and access than to share it using some locking mechanism.
Copying naturally gives unique ownership (simplifies code) and eliminates the possibility of data races.
##### Note
@@ -15199,7 +15312,7 @@ Have strong rules for re-testing in place that covers any change in hardware, op
##### Reason
With the exception of atomics and a few use standard patterns, lock-free programming is really an expert-only topic.
With the exception of atomics and a few other standard patterns, lock-free programming is really an expert-only topic.
Become an expert before shipping lock-free code for others to use.
##### References
@@ -15786,7 +15899,7 @@ Another solution (often better) would be to use a local variable to eliminate ex
##### Note
If you have local "things" that requires cleanup, but is not represented by an object with a destructor, such cleanup must
If you have a local "thing" that requires cleanup, but is not represented by an object with a destructor, such cleanup must
also be done before a `throw`.
Sometimes, [`finally()`](#Re-finally) can make such unsystematic cleanup a bit more manageable.
@@ -16101,7 +16214,7 @@ If we cannot throw an exception, we can simulate this RAII style of resource han
return 0; // zero indicates "good"
}
The problem is of course that the caller now has to remember to test the return value.
The problem is of course that the caller now has to remember to test the return value. To encourage doing so, consider adding a `[[nodiscard]]`.
**See also**: [Discussion](#Sd-???)
@@ -16268,7 +16381,7 @@ This can be messy:
}
Simulating RAII can be non-trivial, especially in functions with multiple resources and multiple possible errors.
A not uncommon technique is to gather cleanup at the end of the function to avoid repetition (note the extra scope around `g2` is undesirable but necessary to make the `goto` version compile):
A not uncommon technique is to gather cleanup at the end of the function to avoid repetition (note that the extra scope around `g2` is undesirable but necessary to make the `goto` version compile):
std::pair<int, error_indicator> user()
{
@@ -16369,7 +16482,7 @@ That's OK, but say that we have checked that this cannot happen and `f` is chang
we now have a crash on our hands unless we change `use()` (and re-test everything).
The snag is that `f()` might be in a library we do not control and the new exception is not anything that `use()` can do
anything about or is in any way interested in.
We can change `use()` to pass `Z` through, but now `use()`'s callers probably needs to be modified.
We can change `use()` to pass `Z` through, but now `use()`'s callers probably need to be modified.
This quickly becomes unmanageable.
Alternatively, we can add a `try`-`catch` to `use()` to map `Z` into an acceptable exception.
This too, quickly becomes unmanageable.
@@ -16399,7 +16512,7 @@ Flag every exception specification.
`catch`-clauses are evaluated in the order they appear and one clause can hide another.
##### Example
##### Example, bad
void f()
{
@@ -16449,13 +16562,13 @@ Prevents accidental or hard-to-notice change of value.
##### Exception
Function arguments are rarely mutated, but also rarely declared const.
To avoid confusion and lots of false positives, don't enforce this rule for function arguments.
Function parameters passed by value are rarely mutated, but also rarely declared `const`.
To avoid confusion and lots of false positives, don't enforce this rule for function parameters.
void f(const char* const p); // pedantic
void g(const int i); // pedantic
void g(const int i) { ... } // pedantic
Note that function parameter is a local variable so changes to it are local.
Note that a function parameter is a local variable so changes to it are local.
##### Enforcement
@@ -16491,8 +16604,8 @@ If it doesn't now, it might do so later without forcing recompilation.
##### Note
There are code/libraries that offer functions that declare a`T*` even though
those function do not modify that `T`.
There are code/libraries that offer functions that declare a `T*` even though
those functions do not modify that `T`.
This is a problem for people modernizing code.
You can
@@ -16564,7 +16677,7 @@ but that should be done only when the called function is supposed to modify the
##### Enforcement
* Flag function that does not modify an object passed by pointer or reference to non-`const`
* Flag a function that does not modify an object passed by pointer or reference to non-`const`
* Flag a function that (using a cast) modifies an object passed by pointer or reference to `const`
### <a name="Rconst-const"></a>Con.4: Use `const` to define objects with values that do not change after construction
@@ -17015,7 +17128,7 @@ or equivalently and more succinctly:
##### Note
"Concepts" are defined in an ISO Technical Specification: [concepts](http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2015/n4553.pdf).
A draft of a set of standard-library concepts can be found in another ISO TS: [ranges](http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2016/n4569.pdf)
A draft of a set of standard-library concepts can be found in another ISO TS: [ranges](http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2016/n4569.pdf).
Concepts are supported in GCC 6.1 and later.
Consequently, we comment out uses of concepts in examples; that is, we use them as formalized comments only.
If you use GCC 6.1 or later, you can uncomment them:
@@ -17120,7 +17233,7 @@ The shorter versions better match the way we speak. Note that many templates don
##### Note
"Concepts" are defined in an ISO Technical Specification: [concepts](http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2015/n4553.pdf).
A draft of a set of standard-library concepts can be found in another ISO TS: [ranges](http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2016/n4569.pdf)
A draft of a set of standard-library concepts can be found in another ISO TS: [ranges](http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2016/n4569.pdf).
Concepts are supported in GCC 6.1 and later.
Consequently, we comment out uses of concepts in examples; that is, we use them as formalized comments only.
If you use a compiler that supports concepts (e.g., GCC 6.1 or later), you can remove the `//`.
@@ -17172,7 +17285,7 @@ This `Addable` violates the mathematical rule that addition is supposed to be co
##### Note
The ability to specify a meaningful semantics is a defining characteristic of a true concept, as opposed to a syntactic constraint.
The ability to specify meaningful semantics is a defining characteristic of a true concept, as opposed to a syntactic constraint.
##### Example (using TS concepts)
@@ -17204,7 +17317,7 @@ Concepts with multiple operations have far lower chance of accidentally matching
##### Enforcement
* Flag single-operation `concepts` when used outside the definition of other `concepts`.
* Flag uses of `enable_if` that appears to simulate single-operation `concepts`.
* Flag uses of `enable_if` that appear to simulate single-operation `concepts`.
### <a name="Rt-complete"></a>T.21: Require a complete set of operations for a concept
@@ -17347,7 +17460,7 @@ An incomplete set of constraints can still be very useful:
detach(p);
}
So a `Balancer` must supply at least thee operations on a tree `Node`,
So a `Balancer` must supply at least these operations on a tree `Node`,
but we are not yet ready to specify detailed semantics because a new kind of balanced tree might require more operations
and the precise general semantics for all nodes is hard to pin down in the early stages of design.
@@ -17456,7 +17569,7 @@ version of `f()`, then delete it.
template<typename T>
void f() = delete;
The compiler will select the overload and emit an appropriate error.
The compiler will select the overload, or emit an appropriate error.
##### Note
@@ -17473,7 +17586,7 @@ Complementary constraints are unfortunately common in `enable_if` code:
##### Note
Complementary requirements on one requirements is sometimes (wrongly) considered manageable.
Complementary requirements on one requirement is sometimes (wrongly) considered manageable.
However, for two or more requirements the number of definitions needs can go up exponentially (2,4,8,16,...):
C1<T> && C2<T>
@@ -17806,7 +17919,7 @@ That is, it is highly visible.
##### Note
This rule should not be necessary, but the committee cannot agree to exclude unconstrained templated from ADL.
This rule should not be necessary, but the committee cannot agree to exclude unconstrained templates from ADL.
Unfortunately this will get many false positives; the standard library violates this widely, by putting many unconstrained templates and types into the single namespace `std`.
@@ -19281,7 +19394,7 @@ Avoid accidentally becoming dependent on implementation details and logically se
`<iostream>` exposes the definition of `std::string` ("why?" makes for a fun trivia question),
but it is not required to do so by transitively including the entire `<string>` header,
resulting in the popular beginner question "why doesn't `getline(cin,s);` work?"
or even an occasional "`string`s cannot be compared with `==`).
or even an occasional "`string`s cannot be compared with `==`").
The solution is to explicitly `#include <string>`:
@@ -19377,7 +19490,7 @@ Library creators should put their headers in a folder and have clients include t
##### Enforcement
A test should identify headers referenced via `""` could be referenced with `<>`.
A test should identify whether headers referenced via `""` could be referenced with `<>`.
### <a name="Rs-namespace"></a>SF.20: Use `namespace`s to express logical structure
@@ -19414,6 +19527,22 @@ It is almost always a bug to mention an unnamed namespace in a header file.
Nothing external can depend on an entity in a nested unnamed namespace.
Consider putting every definition in an implementation source file in an unnamed namespace unless that is defining an "external/exported" entity.
##### Example; bad
static int f();
int g();
static bool h();
int k();
##### Example; good
namespace {
int f();
bool h();
}
int g();
int k();
##### Example
An API class and its members can't live in an unnamed namespace; but any "helper" class or function that is defined in an implementation source file should be at an unnamed namespace scope.
@@ -19623,7 +19752,7 @@ Such loops can be much faster than individually checked element accesses.
memcmp(a.data(), b.data(), 10); // BAD, and contains a length error (length = 10 * sizeof(int))
}
Also, `std::array<>::fill()` or `std::fill()` or even an empty initializer are better candidate than `memset()`.
Also, `std::array<>::fill()` or `std::fill()` or even an empty initializer are better candidates than `memset()`.
##### Example, good
@@ -19859,7 +19988,7 @@ Like any other "plain pointer", a `zstring` should not represent ownership.
There are billions of lines of C++ "out there", most use `char*` and `const char*` without documenting intent.
They are used in a wide variety of ways, including to represent ownership and as generic pointers to memory (instead of `void*`).
It is hard to separate these uses, so this guideline is hard to follow.
This is one of the major sources of bugs in C and C++ programs, so it is worthwhile to follow this guideline wherever feasible..
This is one of the major sources of bugs in C and C++ programs, so it is worthwhile to follow this guideline wherever feasible.
##### Enforcement
@@ -19978,7 +20107,7 @@ Direct expression of an idea minimizes mistakes.
`iostream`s is a type safe, extensible, formatted and unformatted I/O library for streaming I/O.
It supports multiple (and user extensible) buffering strategies and multiple locales.
It can be used for conventional I/O, reading and writing to memory (string streams),
and user-defines extensions, such as streaming across networks (asio: not yet standardized).
and user-defined extensions, such as streaming across networks (asio: not yet standardized).
Iostream rule summary:
@@ -20063,7 +20192,7 @@ If input isn't validated, every function must be written to cope with bad data (
It is often (and often correctly) pointed out that the `printf()` family has two advantages compared to `iostream`s:
flexibility of formatting and performance.
This has to be weighed against `iostream`s advantages of extensibility to handle user-defined types, resilient against security violations,
This has to be weighed against `iostream`s advantages of extensibility to handle user-defined types, resilience against security violations,
implicit memory management, and `locale` handling.
If you need I/O performance, you can almost always do better than `printf()`.
@@ -20202,13 +20331,13 @@ However, a library should not depend on another that depends on it.
# <a name="S-not"></a>NR: Non-Rules and myths
This section contains rules and guidelines that are popular somewhere, but that we deliberately don't recommend.
We know full well that there have been times and places where these rules made sense, and we have used them ourselves at times.
We know perfectly well that there have been times and places where these rules made sense, and we have used them ourselves at times.
However, in the context of the styles of programming we recommend and support with the guidelines, these "non-rules" would do harm.
Even today, there can be contexts where the rules make sense.
For example, lack of suitable tool support can make exceptions unsuitable in hard-real-time systems,
but please don't naïvely trust "common wisdom" (e.g., unsupported statements about "efficiency");
such "wisdom" might be based on decades-old information or experienced from languages with very different properties than C++
such "wisdom" might be based on decades-old information or experiences from languages with very different properties than C++
(e.g., C or Java).
The positive arguments for alternatives to these non-rules are listed in the rules offered as "Alternatives".
@@ -20277,7 +20406,7 @@ In particular, the single-return rule makes it harder to concentrate error check
{
if (x < 0)
return "negative";
else if (x > 0)
if (x > 0)
return "positive";
return "zero";
}
@@ -20359,7 +20488,7 @@ Consider the major objections to exceptions in turn
* Exceptions are inefficient:
Compared to what?
When comparing make sure that the same set of errors are handled and that they are handled equivalently.
In particular, do not compare a program that immediately terminate on seeing an error with a program
In particular, do not compare a program that immediately terminates on seeing an error to a program
that carefully cleans up resources before logging an error.
Yes, some systems have poor exception handling implementations; sometimes, such implementations force us to use
other error-handling approaches, but that's not a fundamental problem with exceptions.
@@ -20377,8 +20506,8 @@ In our opinion, you need RAII to make exception-based error handling simple and
If you are in a hard-real-time system where you must guarantee completion of a task in a given time,
you need tools to back up such guarantees.
As far as we know such tools are not available (at least not to most programmers).
* the exception-handling run-time support takes up too much space
This can be the case in small (usually embedded systems).
* The exception-handling run-time support takes up too much space.
This can be the case in small (usually embedded) systems.
However, before abandoning exceptions consider what space consistent error-handling using error-codes would require
and what failure to catch an error would cost.
@@ -20437,7 +20566,7 @@ and errors (when we didn't deal correctly with semi-constructed objects consiste
{
int mx;
int my;
char * data;
int * data;
public:
// main problem: constructor does not fully construct
Picture(int x, int y)
@@ -20466,7 +20595,7 @@ and errors (when we didn't deal correctly with semi-constructed objects consiste
if (data) {
return false;
}
data = (char*) malloc(mx*my*sizeof(int)); // also bad: owning raw * and malloc
data = (int*) malloc(mx*my*sizeof(int)); // also bad: owning raw * and malloc
return data != nullptr;
}
@@ -20491,7 +20620,7 @@ and errors (when we didn't deal correctly with semi-constructed objects consiste
{
int mx;
int my;
vector<char> data;
vector<int> data;
static int check_size(int size)
{
@@ -20506,7 +20635,7 @@ and errors (when we didn't deal correctly with semi-constructed objects consiste
: mx(check_size(x))
, my(check_size(y))
// now we know x and y have a valid size
, data(mx * my * sizeof(int)) // will throw std::bad_alloc on error
, data(mx * my) // will throw std::bad_alloc on error
{
// picture is ready-to-use
}
@@ -20765,7 +20894,7 @@ or "static type safety."
Each profile is designed to eliminate a class of errors.
Enforcement of "random" rules in isolation is more likely to be disruptive to a code base than delivering a definite improvement.
A "profile" is a set of deterministic and portably enforceable subset rules (i.e., restrictions) that are designed to achieve a specific guarantee.
A "profile" is a set of deterministic and portably enforceable subset of rules (i.e., restrictions) that are designed to achieve a specific guarantee.
"Deterministic" means they require only local analysis and could be implemented in a compiler (though they don't need to be).
"Portably enforceable" means they are like language rules, so programmers can count on different enforcement tools giving the same answer for the same code.
@@ -20936,7 +21065,7 @@ These types allow the user to distinguish between owning and non-owning pointers
These "views" are never owners.
References are never owners (see [R.4](#Rr-ref). Note: References have many opportunities to outlive the objects they refer to (returning a local variable by reference, holding a reference to an element of a vector and doing `push_back`, binding to `std::max(x, y + 1)`, etc. The Lifetime safety profile aims to address those things, but even so `owner<T&>` does not make sense and is discouraged.
References are never owners (see [R.4](#Rr-ref)). Note: References have many opportunities to outlive the objects they refer to (returning a local variable by reference, holding a reference to an element of a vector and doing `push_back`, binding to `std::max(x, y + 1)`, etc). The Lifetime safety profile aims to address those things, but even so `owner<T&>` does not make sense and is discouraged.
The names are mostly ISO standard-library style (lower case and underscore):
@@ -20993,7 +21122,7 @@ Use `not_null<zstring>` 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)
// `Expects` is 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.
These assertions are currently macros (yuck!) and must appear in function definitions (only)
@@ -21005,7 +21134,7 @@ for example, `Expects(p)` will become `[[expects: p]]`.
* `finally` // `finally(f)` makes a `final_action{f}` with a destructor that invokes `f`
* `narrow_cast` // `narrow_cast<T>(x)` is `static_cast<T>(x)`
* `narrow` // `narrow<T>(x)` is `static_cast<T>(x)` if `static_cast<T>(x) == x` or it throws `narrowing_error`
* `narrow` // `narrow<T>(x)` is `static_cast<T>(x)` if `static_cast<T>(x) == x` with no signedness promotions, or it throws `narrowing_error` (e.g., `narrow<unsigned>(-42)` throws)
* `[[implicit]]` // "Marker" to put on single-argument constructors to explicitly make them non-explicit.
* `move_owner` // `p = move_owner(q)` means `p = q` but ???
* `joining_thread` // a RAII style version of `std::thread` that joins.
@@ -21051,7 +21180,7 @@ Most of the concepts below are defined in [the Ranges TS](http://www.open-std.or
Consistent naming and layout are helpful.
If for no other reason because it minimizes "my style is better than your style" arguments.
However, there are many, many, different styles around and people are passionate about them (pro and con).
Also, most real-world projects includes code from many sources, so standardizing on a single style for all code is often impossible.
Also, most real-world projects include code from many sources, so standardizing on a single style for all code is often impossible.
After many requests for guidance from users, we present a set of rules that you might use if you have no better ideas, but the real aim is consistency, rather than any particular rule set.
IDEs and tools can help (as well as hinder).
@@ -21490,7 +21619,7 @@ Avoid multiple blocks of declarations of one access (e.g., `public`) dispersed a
};
The use of macros to declare groups of members often leads to violation of any ordering rules.
However, macros obscures what is being expressed anyway.
However, using macros obscures what is being expressed anyway.
##### Enforcement
@@ -21877,7 +22006,7 @@ If your design wants virtual dispatch into a derived class from a base class con
* *Pass the buck:* Just document that user code must call the post-initialization function right after constructing an object.
* *Post-initialize lazily:* Do it during the first call of a member function. A Boolean flag in the base class tells whether or not post-construction has taken place yet.
* *Use virtual base class semantics:* Language rules dictate that the constructor most-derived class decides which base constructor will be invoked; you can use that to your advantage. (See [\[Taligent94\]](#Taligent94).)
* *Use virtual base class semantics:* Language rules dictate that the constructor of the most-derived class decides which base constructor will be invoked; you can use that to your advantage. (See [\[Taligent94\]](#Taligent94).)
* *Use a factory function:* This way, you can easily force a mandatory invocation of a post-constructor function.
Here is an example of the last option:
@@ -22203,7 +22332,7 @@ In a class holding a reference, you likely need to write the copy constructor an
Resource management rule summary:
* [Provide strong resource safety; that is, never leak anything that you think of as a resource](#Cr-safety)
* [Never throw while holding a resource not owned by a handle](#Cr-never)
* [Never return or throw while holding a resource not owned by a handle](#Cr-never)
* [A "raw" pointer or reference is never a resource handle](#Cr-raw)
* [Never let a pointer outlive the object it points to](#Cr-outlive)
* [Use templates to express containers (and other resource handles)](#Cr-templates)
@@ -22239,7 +22368,7 @@ This class is a resource handle. It manages the lifetime of the `T`s. To do so,
The basic technique for preventing leaks is to have every resource owned by a resource handle with a suitable destructor. A checker can find "naked `new`s". Given a list of C-style allocation functions (e.g., `fopen()`), a checker can also find uses that are not managed by a resource handle. In general, "naked pointers" can be viewed with suspicion, flagged, and/or analyzed. A complete list of resources cannot be generated without human input (the definition of "a resource" is necessarily too general), but a tool can be "parameterized" with a resource list.
### <a name="Cr-never"></a>Discussion: Never throw while holding a resource not owned by a handle
### <a name="Cr-never"></a>Discussion: Never return or throw while holding a resource not owned by a handle
##### Reason