mirror of
https://github.com/isocpp/CppCoreGuidelines.git
synced 2025-12-18 21:24:41 +03:00
Update gh-pages
This commit is contained in:
@@ -4,7 +4,7 @@ layout: default
|
||||
|
||||
# <a name="main"></a>C++ Core Guidelines
|
||||
|
||||
May 29, 2017
|
||||
June 8, 2017
|
||||
|
||||
|
||||
Editors:
|
||||
@@ -95,7 +95,7 @@ You can sample rules for specific language features:
|
||||
[not needed](#Rc-default) --
|
||||
[`explicit`](#Rc-explicit) --
|
||||
[delegating](#Rc-delegating) --
|
||||
[`virtual`](#RC-ctor-virtual)
|
||||
[`virtual`](#Rc-ctor-virtual)
|
||||
* derived `class`:
|
||||
[when to use](#Rh-domain) --
|
||||
[as interface](#Rh-abstract) --
|
||||
@@ -857,7 +857,7 @@ We could check earlier and improve the code:
|
||||
// ...
|
||||
}
|
||||
|
||||
Now, `m<=n` can be checked at the point of call (early) rather than later.
|
||||
Now, `m <= n` can be checked at the point of call (early) rather than later.
|
||||
If all we had was a typo so that we meant to use `n` as the bound, the code could be further simplified (eliminating the possibility of an error):
|
||||
|
||||
void use3(int m)
|
||||
@@ -1878,7 +1878,7 @@ Note: `length()` is, of course, `std::strlen()` in disguise.
|
||||
|
||||
Consider:
|
||||
|
||||
void copy_n(const T* p, T* q, int n); // copy from [p:p+n) to [q:q+n)
|
||||
void copy_n(const T* p, T* q, int n); // copy from [p:p + n) to [q:q + n)
|
||||
|
||||
What if there are fewer than `n` elements in the array pointed to by `q`? Then, we overwrite some probably unrelated memory.
|
||||
What if there are fewer than `n` elements in the array pointed to by `p`? Then, we read some probably unrelated memory.
|
||||
@@ -1968,13 +1968,14 @@ Having many arguments opens opportunities for confusion. Passing lots of argumen
|
||||
|
||||
The two most common reasons why functions have too many parameters are:
|
||||
|
||||
1. *Missing an abstraction.* There is an abstraction missing, so that a compound value is being
|
||||
passed as individual elements instead of as a single object that enforces an invariant.
|
||||
This not only expands the parameter list, but it leads to errors because the component values
|
||||
are no longer protected by an enforced invariant.
|
||||
1. *Missing an abstraction.*
|
||||
There is an abstraction missing, so that a compound value is being
|
||||
passed as individual elements instead of as a single object that enforces an invariant.
|
||||
This not only expands the parameter list, but it leads to errors because the component values
|
||||
are no longer protected by an enforced invariant.
|
||||
|
||||
2. *Violating "one function, one responsibility."* The function is trying to do more than one
|
||||
job and should probably be refactored.
|
||||
2. *Violating "one function, one responsibility."*
|
||||
The function is trying to do more than one job and should probably be refactored.
|
||||
|
||||
##### Example
|
||||
|
||||
@@ -2044,13 +2045,13 @@ Adjacent arguments of the same type are easily swapped by mistake.
|
||||
|
||||
Consider:
|
||||
|
||||
void copy_n(T* p, T* q, int n); // copy from [p:p+n) to [q:q+n)
|
||||
void copy_n(T* p, T* q, int n); // copy from [p:p + n) to [q:q + n)
|
||||
|
||||
This is a nasty variant of a K&R C-style interface. It is easy to reverse the "to" and "from" arguments.
|
||||
|
||||
Use `const` for the "from" argument:
|
||||
|
||||
void copy_n(const T* p, T* q, int n); // copy from [p:p+n) to [q:q+n)
|
||||
void copy_n(const T* p, T* q, int n); // copy from [p:p + n) to [q:q + n)
|
||||
|
||||
##### Exception
|
||||
|
||||
@@ -2393,8 +2394,8 @@ Functions with complex control structures are more likely to be long and more li
|
||||
|
||||
Consider:
|
||||
|
||||
double simpleFunc(double val, int flag1, int flag2)
|
||||
// simpleFunc: takes a value and calculates the expected ASIC output,
|
||||
double simple_func(double val, int flag1, int flag2)
|
||||
// simple_func: takes a value and calculates the expected ASIC output,
|
||||
// given the two mode flags.
|
||||
{
|
||||
double intermediate;
|
||||
@@ -2437,8 +2438,8 @@ We can refactor:
|
||||
// ???
|
||||
}
|
||||
|
||||
double simpleFunc(double val, int flag1, int flag2)
|
||||
// simpleFunc: takes a value and calculates the expected ASIC output,
|
||||
double simple_func(double val, int flag1, int flag2)
|
||||
// simple_func: takes a value and calculates the expected ASIC output,
|
||||
// given the two mode flags.
|
||||
{
|
||||
if (flag1 > 0)
|
||||
@@ -2531,7 +2532,7 @@ Most computation is best done at run time.
|
||||
|
||||
Any API that may eventually depend on high-level runtime configuration or
|
||||
business logic should not be made `constexpr`. Such customization can not be
|
||||
evaluated by the compiler, and any `constexpr` functions that depended upon
|
||||
evaluated by the compiler, and any `constexpr` functions that depended upon
|
||||
that API would have to be refactored or drop `constexpr`.
|
||||
|
||||
##### Enforcement
|
||||
@@ -3195,7 +3196,7 @@ Informal/non-explicit ranges are a source of errors.
|
||||
##### Note
|
||||
|
||||
Ranges are extremely common in C++ code. Typically, they are implicit and their correct use is very hard to ensure.
|
||||
In particular, given a pair of arguments `(p, n)` designating an array \[`p`:`p+n`),
|
||||
In particular, given a pair of arguments `(p, n)` designating an array \[`p`:`p + n`),
|
||||
it is in general impossible to know if there really are `n` elements to access following `*p`.
|
||||
`span<T>` and `span_p<T>` are simple helper classes designating a \[`p`:`q`) range and a range starting with `p` and ending with the first element for which a predicate is true, respectively.
|
||||
|
||||
@@ -4190,7 +4191,7 @@ For example, a derived class might be allowed to skip a run-time check because i
|
||||
int mem(int x, int y)
|
||||
{
|
||||
/* ... do something ... */
|
||||
return do_bar(x+y); // OK: derived class can bypass check
|
||||
return do_bar(x + y); // OK: derived class can bypass check
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6968,27 +6969,27 @@ This kind of "vector" isn't meant to be used as a base class at all.
|
||||
##### Reason
|
||||
|
||||
`protected` data is a source of complexity and errors.
|
||||
`protected` data complicated the statement of invariants.
|
||||
`protected` data inherently violates the guidance against putting data in base classes, which usually leads to having to deal virtual inheritance as well.
|
||||
`protected` data complicates the statement of invariants.
|
||||
`protected` data inherently violates the guidance against putting data in base classes, which usually leads to having to deal with virtual inheritance as well.
|
||||
|
||||
##### Example, bad
|
||||
|
||||
class Shape {
|
||||
public:
|
||||
class Shape {
|
||||
public:
|
||||
// ... interface functions ...
|
||||
protected:
|
||||
protected:
|
||||
// data for use in derived classes:
|
||||
Color fill_color;
|
||||
Color edge_color;
|
||||
Style st;
|
||||
};
|
||||
};
|
||||
|
||||
Now it is up to every derived `Shape` to manipulate the protected data correctly.
|
||||
This has been popular, but also a major source of maintenance problems.
|
||||
In a large class hierarchy, the consistent use of protected data is hard to maintain because there can be a lot of code,
|
||||
spread over a lot of classes.
|
||||
The set of classes that can touch that data is open: anyone can derive a new class and start manipulating the protected data.
|
||||
Often, it is not possible to examine the complete set of classes so any change to the representation of the class becomes infeasible.
|
||||
Often, it is not possible to examine the complete set of classes, so any change to the representation of the class becomes infeasible.
|
||||
There is no enforced invariant for the protected data; it is much like a set of global variables.
|
||||
The protected data has de facto become global to a large body of code.
|
||||
|
||||
@@ -7056,7 +7057,7 @@ Especially to break apart monolithic interfaces into "aspects" of behavior suppo
|
||||
};
|
||||
|
||||
`istream` provides the interface to input operations; `ostream` provides the interface to output operations.
|
||||
`iostream` provides the union of the `istream` and `ostream` interfaces and the synchronization needed to allow both on a single stream.
|
||||
`iostream` provides the union of the `istream` and `ostream` interfaces and the synchronization needed to allow both on a single stream.
|
||||
|
||||
##### Note
|
||||
|
||||
@@ -7085,7 +7086,7 @@ If the operations are virtual the use of inheritance is necessary, if not using
|
||||
};
|
||||
|
||||
`istream` provides the interface to input operations (and some data); `ostream` provides the interface to output operations (and some data).
|
||||
`iostream` provides the union of the `istream` and `ostream` interfaces and the synchronization needed to allow both on a single stream.
|
||||
`iostream` provides the union of the `istream` and `ostream` interfaces and the synchronization needed to allow both on a single stream.
|
||||
|
||||
##### Note
|
||||
|
||||
@@ -7094,13 +7095,13 @@ This a relatively rare use because implementation can often be organized into a
|
||||
##### Example
|
||||
|
||||
Sometimes, an "implementation attribute" is more like a "mixin" that determine the behavior of an implementation and inject
|
||||
members to enable the implementation of the policies it requires.
|
||||
members to enable the implementation of the policies it requires.
|
||||
For example, see `std::enable_shared_from_this`
|
||||
or various bases from boost.intrusive (e.g. `list_base_hook` or `intrusive_ref_counter`).
|
||||
|
||||
##### Enforcement
|
||||
|
||||
???
|
||||
???
|
||||
|
||||
### <a name="Rh-vbase"></a>C.137: Use `virtual` bases to avoid overly general base classes
|
||||
|
||||
@@ -7171,7 +7172,7 @@ Without a using declaration, member functions in the derived class hide the enti
|
||||
};
|
||||
class D: public B {
|
||||
public:
|
||||
int f(int i) override { std::cout << "f(int): "; return i+1; }
|
||||
int f(int i) override { std::cout << "f(int): "; return i + 1; }
|
||||
};
|
||||
int main()
|
||||
{
|
||||
@@ -7184,7 +7185,7 @@ Without a using declaration, member functions in the derived class hide the enti
|
||||
|
||||
class D: public B {
|
||||
public:
|
||||
int f(int i) override { std::cout << "f(int): "; return i+1; }
|
||||
int f(int i) override { std::cout << "f(int): "; return i + 1; }
|
||||
using B::f; // exposes f(double)
|
||||
};
|
||||
|
||||
@@ -9438,6 +9439,7 @@ Expression rules:
|
||||
* [ES.62: Don't compare pointers into different arrays](#Res-arr2)
|
||||
* [ES.63: Don't slice](#Res-slice)
|
||||
* [ES.64: Use the `T{e}`notation for construction](#Res-construct)
|
||||
* [ES.65: Don't dereference an invalid pointer](#Res-deref)
|
||||
|
||||
Statement rules:
|
||||
|
||||
@@ -9875,7 +9877,7 @@ When concepts become available, we can (and should) be more specific about the t
|
||||
ForwardIterator p = algo(x, y, z);
|
||||
|
||||
##### Example (C++17)
|
||||
|
||||
|
||||
auto [ quotient, remainder ] = div(123456, 73); // break out the members of the div_t result
|
||||
|
||||
##### Enforcement
|
||||
@@ -10388,17 +10390,17 @@ Readability and safety.
|
||||
|
||||
As an optimization, you may want to reuse a buffer as a scratch pad, but even then prefer to limit the variable's scope as much as possible and be careful not to cause bugs from data left in a recycled buffer as this is a common source of security bugs.
|
||||
|
||||
{
|
||||
void write_to_file() {
|
||||
std::string buffer; // to avoid reallocations on every loop iteration
|
||||
for (auto& o : objects)
|
||||
{
|
||||
// First part of the work.
|
||||
generateFirstString(buffer, o);
|
||||
writeToFile(buffer);
|
||||
generate_first_String(buffer, o);
|
||||
write_to_file(buffer);
|
||||
|
||||
// Second part of the work.
|
||||
generateSecondString(buffer, o);
|
||||
writeToFile(buffer);
|
||||
generate_second_string(buffer, o);
|
||||
write_to_file(buffer);
|
||||
|
||||
// etc...
|
||||
}
|
||||
@@ -11064,11 +11066,11 @@ There are exceedingly clever used of this "idiom", but they are far rarer than t
|
||||
|
||||
##### Note
|
||||
|
||||
Unnamed function arguments are fine.
|
||||
Unnamed function arguments are fine.
|
||||
|
||||
##### Enforcement
|
||||
|
||||
Flag statements that are just a temporary
|
||||
Flag statements that are just a temporary
|
||||
|
||||
### <a name="Res-empty"></a>ES.85: Make empty statements visible
|
||||
|
||||
@@ -11244,22 +11246,22 @@ Access into an array with known bounds using a constant as a subscript can be va
|
||||
{
|
||||
if (count < 2) return;
|
||||
|
||||
int* q = p + 1; // BAD
|
||||
int* q = p + 1; // BAD
|
||||
|
||||
ptrdiff_t d;
|
||||
int n;
|
||||
d = (p - &n); // OK
|
||||
d = (q - p); // OK
|
||||
d = (p - &n); // OK
|
||||
d = (q - p); // OK
|
||||
|
||||
int n = *p++; // BAD
|
||||
int n = *p++; // BAD
|
||||
|
||||
if (count < 6) return;
|
||||
|
||||
p[4] = 1; // BAD
|
||||
p[4] = 1; // BAD
|
||||
|
||||
p[count - 1] = 2; // BAD
|
||||
p[count - 1] = 2; // BAD
|
||||
|
||||
use(&p[0], 3); // BAD
|
||||
use(&p[0], 3); // BAD
|
||||
}
|
||||
|
||||
##### Example, good
|
||||
@@ -11268,17 +11270,17 @@ Access into an array with known bounds using a constant as a subscript can be va
|
||||
{
|
||||
if (a.length() < 2) return;
|
||||
|
||||
int n = a[0]; // OK
|
||||
int n = a[0]; // OK
|
||||
|
||||
span<int> q = a.subspan(1); // OK
|
||||
|
||||
if (a.length() < 6) return;
|
||||
|
||||
a[4] = 1; // OK
|
||||
a[4] = 1; // OK
|
||||
|
||||
a[count - 1] = 2; // OK
|
||||
a[count - 1] = 2; // OK
|
||||
|
||||
use(a.data(), 3); // OK
|
||||
use(a.data(), 3); // OK
|
||||
}
|
||||
|
||||
##### Note
|
||||
@@ -11593,12 +11595,12 @@ What would you think this fragment prints? The result is at best implementation
|
||||
|
||||
2 0 4611686018427387904
|
||||
|
||||
Adding
|
||||
Adding
|
||||
|
||||
*q = 666;
|
||||
cout << d << ' ' << *p << ' ' << *q << '\n';
|
||||
|
||||
I got
|
||||
I got
|
||||
|
||||
3.29048e-321 666 666
|
||||
|
||||
@@ -12208,6 +12210,147 @@ The main problem left is to find a suitable name for `Count`.
|
||||
|
||||
Flag the C-style `(T)e` and functional-style `T(e)` casts.
|
||||
|
||||
|
||||
### <a name="Res-deref"></a>ES.65: Don't dereference an invalid pointer
|
||||
|
||||
##### Reason
|
||||
|
||||
Dereferencing an invalid pointer, such as `nullptr`, is undefined behavior, typically leading to immediate crashes,
|
||||
wrong results, or memory corruption.
|
||||
|
||||
##### Note
|
||||
|
||||
This rule is an obvious and well-known language rule, but can be hard to follow.
|
||||
It takes good coding style, library support, and static analysis to eliminate violations without major overhead.
|
||||
This is a major part of the discussion of [C++'s resource- and type-safety model](#Stroustrup15).
|
||||
|
||||
See also
|
||||
|
||||
* Use [RAII](#Rr-raii) to avoid lifetime problems.
|
||||
* Use [unique_ptr](#Rf-unique_ptr) to avoid lifetime problems.
|
||||
* Use [shared_ptr](#Rf-shared_ptr) to avoid lifetime problems.
|
||||
* Use [references](#Rf-ptr-ref) when `nullptr` isn't a possibility.
|
||||
* Use [not_null](#Rf-not_null) to catch unexpected `nullptr` early.
|
||||
* Use the [bounds profile](#SS-bounds) to avoid range errors.
|
||||
|
||||
|
||||
##### Example
|
||||
|
||||
void f()
|
||||
{
|
||||
int x = 0;
|
||||
int* p = &x;
|
||||
|
||||
if (condition()) {
|
||||
int y = 0;
|
||||
p = &y;
|
||||
} // invalidates p
|
||||
|
||||
*p = 42; // BAD, p might be invalid if the branch was taken
|
||||
}
|
||||
|
||||
To resolve the problem, either extend the lifetime of the object the pointer is intended to refer to, or shorten the lifetime of the pointer (move the dereference to before the pointed-to object's lifetime ends).
|
||||
|
||||
void f1()
|
||||
{
|
||||
int x = 0;
|
||||
int* p = &x;
|
||||
|
||||
int y = 0;
|
||||
if (condition()) {
|
||||
p = &y;
|
||||
}
|
||||
|
||||
*p = 42; // OK, p points to x or y and both are still in scope
|
||||
}
|
||||
|
||||
Unfortunately, most invalid pointer problems are harder to spot and harder to fix.
|
||||
|
||||
##### Example
|
||||
|
||||
void f(int* p)
|
||||
{
|
||||
int x = *p; // BAD: how do we know that p is valid?
|
||||
}
|
||||
|
||||
There is a huge amount of such code.
|
||||
Most works -- after lots of testing -- but in isolation it is impossible to tell whether `p` could be the `nullptr`.
|
||||
Consequently, it this is also a major source of errors.
|
||||
There are many approaches to dealing with this potential problem:
|
||||
|
||||
void f1(int* p) // deal with nullptr
|
||||
{
|
||||
if (p == nullptr) {
|
||||
// deal with nullptr (allocate, return, throw, make p point to something, whatever
|
||||
}
|
||||
int x = *p;
|
||||
}
|
||||
|
||||
There are two potential problems with testing for `nullptr`:
|
||||
|
||||
* it is not always obvious what to do what to do if we find `nullptr`
|
||||
* the test can be redundant and/or relatively expensive
|
||||
* it is not obvious if the test is to protect against a violation or part of the required logic.
|
||||
|
||||
|
||||
void f2(int* p) // state that p is not supposed to be nullptr
|
||||
{
|
||||
assert(p != nullptr);
|
||||
int x = *p;
|
||||
}
|
||||
|
||||
This would carry a cost only when the assertion checking was enabled and would give a compiler/analyzer useful information.
|
||||
This would work even better if/when C++ gets direct support for contracts:
|
||||
|
||||
void f3(int* p) // state that p is not supposed to be nullptr
|
||||
[[expects: p != nullptr]]
|
||||
{
|
||||
int x = *p;
|
||||
}
|
||||
|
||||
Alternatively, we could use `gsl::not_null` to ensure that `p` is not the `nullptr`.
|
||||
|
||||
void f(not_null<int*> p)
|
||||
{
|
||||
int x = *p;
|
||||
}
|
||||
|
||||
These remedies take care of `nullptr` only.
|
||||
Remember that there are other ways of getting an invalid pointer.
|
||||
|
||||
##### Example
|
||||
|
||||
void f(int* p) // old code, doesn't use owner
|
||||
{
|
||||
delete p;
|
||||
}
|
||||
|
||||
void g() // old code: uses naked new
|
||||
{
|
||||
auto q = new int{7};
|
||||
f(q);
|
||||
int x = *q; // BAD: dereferences invalid pointer
|
||||
}
|
||||
|
||||
##### Example
|
||||
|
||||
void f()
|
||||
{
|
||||
vector<int> v(10);
|
||||
int* p = &v[5];
|
||||
v.push_back(99); // could reallocate v's elements
|
||||
int x = *p; // BAD: dereferences potentially invalid pointer
|
||||
}
|
||||
|
||||
##### Enforcement
|
||||
|
||||
This rule is part of the [lifetime profile](#Pro.lifetime)
|
||||
|
||||
* Flag a dereference of a pointer that points to an object that has gone out of scope
|
||||
* Flag a dereference of a pointer that may have been invalidated by assigning a `nullptr`
|
||||
* Flag a dereference of a pointer that may have been invalidated by a `delete`
|
||||
* Flag a dereference to a pointer to a container element that may have been invalidated by dereference
|
||||
|
||||
## <a name="SS-numbers"></a>Arithmetic
|
||||
|
||||
### <a name="Res-mix"></a>ES.100: Don't mix signed and unsigned arithmetic
|
||||
@@ -12264,7 +12407,7 @@ can be surprising for many programmers.
|
||||
##### Reason
|
||||
|
||||
Because most arithmetic is assumed to be signed;
|
||||
`x-y` yields a negative number when `y>x` except in the rare cases where you really want modulo arithmetic.
|
||||
`x - y` yields a negative number when `y > x` except in the rare cases where you really want modulo arithmetic.
|
||||
|
||||
##### Example
|
||||
|
||||
@@ -12274,23 +12417,23 @@ This is even more true for mixed signed and unsigned arithmetic.
|
||||
template<typename T, typename T2>
|
||||
T subtract(T x, T2 y)
|
||||
{
|
||||
return x-y;
|
||||
return x - y;
|
||||
}
|
||||
|
||||
void test()
|
||||
{
|
||||
int s = 5;
|
||||
unsigned int us = 5;
|
||||
cout << subtract(s, 7) << '\n'; // -2
|
||||
cout << subtract(us, 7u) << '\n'; // 4294967294
|
||||
cout << subtract(s, 7u) << '\n'; // -2
|
||||
cout << subtract(us, 7) << '\n'; // 4294967294
|
||||
cout << subtract(s, us+2) << '\n'; // -2
|
||||
cout << subtract(us, s+2) << '\n'; // 4294967294
|
||||
cout << subtract(s, 7) << '\n'; // -2
|
||||
cout << subtract(us, 7u) << '\n'; // 4294967294
|
||||
cout << subtract(s, 7u) << '\n'; // -2
|
||||
cout << subtract(us, 7) << '\n'; // 4294967294
|
||||
cout << subtract(s, us + 2) << '\n'; // -2
|
||||
cout << subtract(us, s + 2) << '\n'; // 4294967294
|
||||
}
|
||||
|
||||
Here we have been very explicit about what's happening,
|
||||
but if you had seen `us-(s+2)` or `s+=2; ... us-s`, would you reliably have suspected that the result would print as `4294967294`?
|
||||
but if you had seen `us - (s + 2)` or `s += 2; ...; us - s`, would you reliably have suspected that the result would print as `4294967294`?
|
||||
|
||||
##### Exception
|
||||
|
||||
@@ -12305,10 +12448,10 @@ The build-in array uses signed types for subscripts.
|
||||
This makes surprises (and bugs) inevitable.
|
||||
|
||||
int a[10];
|
||||
for (int i=0; i < 10; ++i) a[i]=i;
|
||||
for (int i = 0; i < 10; ++i) a[i] = i;
|
||||
vector<int> v(10);
|
||||
// compares signed to unsigned; some compilers warn
|
||||
for (int i=0; v.size() < 10; ++i) v[i]=i;
|
||||
for (int i = 0; v.size() < 10; ++i) v[i] = i;
|
||||
|
||||
int a2[-2]; // error: negative size
|
||||
|
||||
@@ -12495,13 +12638,13 @@ To enable better error detection.
|
||||
|
||||
vector<int> vec {1, 2, 3, 4, 5};
|
||||
|
||||
for (int i=0; i < vec.size(); i+=2) // mix int and unsigned
|
||||
for (int i = 0; i < vec.size(); i += 2) // mix int and unsigned
|
||||
cout << vec[i] << '\n';
|
||||
for (unsigned i=0; i < vec.size(); i+=2) // risk wraparound
|
||||
for (unsigned i = 0; i < vec.size(); i += 2) // risk wraparound
|
||||
cout << vec[i] << '\n';
|
||||
for (vector<int>::size_type i=0; i < vec.size(); i+=2) // verbose
|
||||
for (vector<int>::size_type i = 0; i < vec.size(); i += 2) // verbose
|
||||
cout << vec[i] << '\n';
|
||||
for (auto i=0; i < vec.size(); i+=2) // mix int and unsigned
|
||||
for (auto i = 0; i < vec.size(); i += 2) // mix int and unsigned
|
||||
cout << vec[i] << '\n';
|
||||
|
||||
##### Note
|
||||
@@ -13923,7 +14066,7 @@ Flag all unnamed `lock_guard`s and `unique_lock`s.
|
||||
|
||||
##### Reason
|
||||
|
||||
It should be obvious to a reader that the data is to be guarded and how. This decreases the chance of the wrong mutex being locked, or the mutex not being locked.
|
||||
It should be obvious to a reader that the data is to be guarded and how. This decreases the chance of the wrong mutex being locked, or the mutex not being locked.
|
||||
|
||||
Using a `synchronized_value<T>` ensures that the data has a mutex, and the right mutex is locked when the data is accessed.
|
||||
See the [WG21 proposal](http://wg21.link/p0290)) to add `synchronized_value` to a future TS or revision of the C++ standard.
|
||||
@@ -15253,7 +15396,7 @@ Exception specifications make error handling brittle, impose a run-time cost, an
|
||||
// ...
|
||||
}
|
||||
|
||||
if `f()` throws an exception different from `X` and `Y` the unexpected handler is invoked, which by default terminates.
|
||||
If `f()` throws an exception different from `X` and `Y` the unexpected handler is invoked, which by default terminates.
|
||||
That's OK, but say that we have checked that this cannot happen and `f` is changed to throw a new exception `Z`,
|
||||
we now have a crash on our hands unless we change `use()` (and re-test everything).
|
||||
The snag is that `f()` may be in a library we do not control and the new exception is not anything that `use()` can do
|
||||
@@ -17733,7 +17876,7 @@ Source file rule summary:
|
||||
* [SF.7: Don't write `using namespace` in a header file](#Rs-using-directive)
|
||||
* [SF.8: Use `#include` guards for all `.h` files](#Rs-guards)
|
||||
* [SF.9: Avoid cyclic dependencies among source files](#Rs-cycles)
|
||||
* [SF.10: Avoid dependencies on implicitly `#included` names](#Rs-implicit)
|
||||
* [SF.10: Avoid dependencies on implicitly `#include`d names](#Rs-implicit)
|
||||
|
||||
* [SF.20: Use `namespace`s to express logical structure](#Rs-namespace)
|
||||
* [SF.21: Don't use an unnamed (anonymous) namespace in a header](#Rs-unnamed)
|
||||
@@ -17943,11 +18086,11 @@ The argument-type error for `bar` cannot be caught until link time because of th
|
||||
|
||||
##### Example
|
||||
|
||||
#include<string>
|
||||
#include<vector>
|
||||
#include<iostream>
|
||||
#include<memory>
|
||||
#include<algorithm>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <iostream>
|
||||
#include <memory>
|
||||
#include <algorithm>
|
||||
|
||||
using namespace std;
|
||||
|
||||
@@ -17960,7 +18103,7 @@ could be distracting.
|
||||
|
||||
The use of `using namespace std;` leaves the programmer open to a name clash with a name from the standard library
|
||||
|
||||
#include<cmath>
|
||||
#include <cmath>
|
||||
using namespace std;
|
||||
|
||||
int g(int x)
|
||||
@@ -18002,7 +18145,7 @@ Doing so takes away an `#include`r's ability to effectively disambiguate and to
|
||||
|
||||
// user.cpp
|
||||
#include "bad.h"
|
||||
|
||||
|
||||
bool copy(/*... some parameters ...*/); // some function that happens to be named copy
|
||||
|
||||
int main() {
|
||||
@@ -18069,7 +18212,7 @@ Eliminate cycles; don't just break them with `#include` guards.
|
||||
Flag all cycles.
|
||||
|
||||
|
||||
### <a name="Rs-implicit"></a>SF.10: Avoid dependencies on implicitly `#included` names
|
||||
### <a name="Rs-implicit"></a>SF.10: Avoid dependencies on implicitly `#include`d names
|
||||
|
||||
##### Reason
|
||||
|
||||
@@ -18092,12 +18235,12 @@ Avoid accidentally becoming dependent on implementation details and logically se
|
||||
}
|
||||
}
|
||||
|
||||
<iostream> exposes the definition of `std::string` ("why?" makes for a fun trivia question),
|
||||
`<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 `==`).
|
||||
|
||||
The solution is to explicitly `#include<string>`:
|
||||
The solution is to explicitly `#include <string>`:
|
||||
|
||||
#include <iostream>
|
||||
#include <string>
|
||||
@@ -18416,7 +18559,7 @@ The important issue of non-ASCII character sets and encodings (e.g., `wchar_t`,
|
||||
See also [regular expressions](#SS-regex).
|
||||
|
||||
Here, we use "sequence of characters" or "string" to refer to a sequence of characters meant to be read as text (somehow, eventually).
|
||||
We don't consider
|
||||
We don't consider
|
||||
|
||||
String summary:
|
||||
|
||||
@@ -18424,11 +18567,11 @@ String summary:
|
||||
* [SL.str.2: Use `std::string_view` or `gsl::string_span` to refer to character sequences](#Rstr-view)
|
||||
* [SL.str.3: Use `zstring` or `czstring` to refer to a C-style, zero-terminated, sequence of characters](#Rstr-zstring)
|
||||
* [SL.str.4: Use `char*` to refer to a single character](#Rstr-char*)
|
||||
* [Sl.str.5: Use `std::byte` to refer to byte values that do not necessarily represent characters](#Rstr-byte)
|
||||
* [SL.str.5: Use `std::byte` to refer to byte values that do not necessarily represent characters](#Rstr-byte)
|
||||
|
||||
* [Sl.str.10: Use `std::string` when you need to perform locale-sensitive string operations](#Rstr-locale)
|
||||
* [Sl.str.11: Use `gsl::string_span` rather than `std::string_view` when you need to mutate a string](#Rstr-span)
|
||||
* [Sl.str.12: Use the `s` suffix for string literals meant to be standard-library `string`s](#Rstr-s)
|
||||
* [SL.str.10: Use `std::string` when you need to perform locale-sensitive string operations](#Rstr-locale)
|
||||
* [SL.str.11: Use `gsl::string_span` rather than `std::string_view` when you need to mutate a string](#Rstr-span)
|
||||
* [SL.str.12: Use the `s` suffix for string literals meant to be standard-library `string`s](#Rstr-s)
|
||||
|
||||
See also
|
||||
|
||||
@@ -18484,11 +18627,11 @@ Don't use C-style strings for operations that require non-trivial memory managem
|
||||
{
|
||||
int l1 = strlen(s1);
|
||||
int l2 = strlen(s2);
|
||||
char* p = (char*)malloc(l1+l2+2);
|
||||
char* p = (char*) malloc(l1 + l2 + 2);
|
||||
strcpy(p, s1, l1);
|
||||
p[l1] = '.';
|
||||
strcpy(p+l1+1, s2, l2);
|
||||
p[l1+l2+1] = 0;
|
||||
strcpy(p + l1 + 1, s2, l2);
|
||||
p[l1 + l2 + 1] = 0;
|
||||
return res;
|
||||
}
|
||||
|
||||
@@ -18599,10 +18742,10 @@ The array `arr` is not a C-style string because it is not zero-terminated.
|
||||
See [`zstring`](#Rstr-zstring), [`string`](#Rstr-string), and [`string_span`](#Rstr-view).
|
||||
|
||||
##### Enforcement
|
||||
|
||||
|
||||
* Flag uses of `[]` on a `char*`
|
||||
|
||||
### <a name="Rstr-byte"></a>Sl.str.5: Use `std::byte` to refer to byte values that do not necessarily represent characters
|
||||
### <a name="Rstr-byte"></a>SL.str.5: Use `std::byte` to refer to byte values that do not necessarily represent characters
|
||||
|
||||
##### Reason
|
||||
|
||||
@@ -18620,9 +18763,9 @@ C++17
|
||||
##### Enforcement
|
||||
|
||||
???
|
||||
|
||||
|
||||
### <a name="Rstr-locale"></a>Sl.str.10: Use `std::string` when you need to perform locale-sensitive string operations
|
||||
|
||||
### <a name="Rstr-locale"></a>SL.str.10: Use `std::string` when you need to perform locale-sensitive string operations
|
||||
|
||||
##### Reason
|
||||
|
||||
@@ -18640,7 +18783,7 @@ C++17
|
||||
|
||||
???
|
||||
|
||||
### <a name="Rstr-span"></a>Sl.str.11: Use `gsl::string_span` rather than `std::string_view` when you need to mutate a string
|
||||
### <a name="Rstr-span"></a>SL.str.11: Use `gsl::string_span` rather than `std::string_view` when you need to mutate a string
|
||||
|
||||
##### Reason
|
||||
|
||||
@@ -18658,7 +18801,7 @@ C++17
|
||||
|
||||
The compiler will flag attempts to write to a `string_view`.
|
||||
|
||||
### <a name="Rstr-s"></a>Sl.str.12: Use the `s` suffix for string literals meant to be standard-library `string`s
|
||||
### <a name="Rstr-s"></a>SL.str.12: Use the `s` suffix for string literals meant to be standard-library `string`s
|
||||
|
||||
##### Reason
|
||||
|
||||
@@ -18693,7 +18836,7 @@ Iostream rule summary:
|
||||
* [SL.io.1: Use character-level input only when you have to](#Rio-low)
|
||||
* [SL.io.2: When reading, always consider ill-formed input](#Rio-validate)
|
||||
* [SL.io.3: Prefer iostreams for I/O](#Rio-streams)
|
||||
* [SL.io.10: Unless you use `printf`-family functions call `ios_base::sync_with_stdio(false)`](#Rio-sync)
|
||||
* [SL.io.10: Unless you use `printf`-family functions call `ios_base::sync_with_stdio(false)`](#Rio-sync)
|
||||
* [SL.io.50: Avoid `endl`](#Rio-endl)
|
||||
* [???](#???)
|
||||
|
||||
@@ -18886,7 +19029,7 @@ Architectural rule summary:
|
||||
|
||||
A library is a collection of declarations and definitions maintained, documented, and shipped together.
|
||||
A library could be a set of headers (a "header only library") or a set of headers plus a set of object files.
|
||||
A library can be statically or dynamically linked into a program, or it may be `#included`
|
||||
A library can be statically or dynamically linked into a program, or it may be `#include`d
|
||||
|
||||
|
||||
### <a name="Ra-dag"></a>A.4: There should be no cycles among libraries
|
||||
@@ -19379,7 +19522,7 @@ Enabling a profile is implementation defined; typically, it is set in the analys
|
||||
|
||||
To suppress enforcement of a profile check, place a `suppress` annotation on a language contract. For example:
|
||||
|
||||
[[suppress(bounds)]] char* raw_find(char* p, int n, char x) // find x in p[0]..p[n-1]
|
||||
[[suppress(bounds)]] char* raw_find(char* p, int n, char x) // find x in p[0]..p[n - 1]
|
||||
{
|
||||
// ...
|
||||
}
|
||||
@@ -19460,146 +19603,26 @@ and "mysterious values."
|
||||
|
||||
## <a name="SS-lifetime"></a>Pro.lifetime: Lifetime safety profile
|
||||
|
||||
See /docs folder for the initial design. The detailed formal rules are in progress (as of May 2017).
|
||||
Accessing through a pointer that doesn't point to anything is a major source of errors,
|
||||
and very hard to avoid in many traditional C or C++ styles of programming.
|
||||
For example, a pointer may be uninitialized, the `nullptr`, point beyond the range of an array, or to a deleted object.
|
||||
|
||||
The following are specific rules that are being enforced.
|
||||
See /docs folder for the initial design. The detailed formal rules are in progress (as of May 2017).
|
||||
|
||||
Lifetime safety profile summary:
|
||||
|
||||
* [Lifetime.1: Don't dereference a possibly invalid pointer.](#Pro-lifetime-invalid-deref)
|
||||
* [Lifetime.2: Don't dereference a possibly null pointer.](#Pro-lifetime-null-deref)
|
||||
* [Lifetime.3: Don't pass a possibly invalid pointer to a function.](#Pro-lifetime-invalid-argument)
|
||||
* <a href="Pro-lifetime-invalid-deref"></a>Lifetime.1: Don't dereference a possibly invalid pointer:
|
||||
[detect or avoid](#Res-deref).
|
||||
|
||||
??? These rules will be moved into the main-line sections of these guidelines ???
|
||||
##### Impact
|
||||
|
||||
### <a name="Pro-lifetime-invalid-deref"></a>Lifetime.1: Don't dereference a possibly invalid pointer.
|
||||
|
||||
##### Reason
|
||||
|
||||
It is undefined behavior.
|
||||
|
||||
To resolve the problem, either extend the lifetime of the object the pointer is intended to refer to, or shorten the lifetime of the pointer (move the dereference to before the pointed-to object's lifetime ends).
|
||||
|
||||
##### Example, bad
|
||||
|
||||
void f()
|
||||
{
|
||||
int x = 0;
|
||||
int* p = &x;
|
||||
|
||||
if (condition()) {
|
||||
int y = 0;
|
||||
p = &y;
|
||||
} // invalidates p
|
||||
|
||||
*p = 42; // BAD, p might be invalid if the branch was taken
|
||||
}
|
||||
|
||||
##### Example, good
|
||||
|
||||
void f()
|
||||
{
|
||||
int x = 0;
|
||||
int* p = &x;
|
||||
|
||||
int y = 0;
|
||||
if (condition()) {
|
||||
p = &y;
|
||||
}
|
||||
|
||||
*p = 42; // OK, p points to x or y and both are still in scope
|
||||
}
|
||||
|
||||
##### Enforcement
|
||||
|
||||
* Issue a diagnostic for any dereference of a pointer that could have been invalidated (could point to an object that was destroyed) along a local code path leading to the dereference. To fix: Extend the lifetime of the pointed-to object, or move the dereference to before the pointed-to object's lifetime ends.
|
||||
|
||||
|
||||
|
||||
### <a name="Pro-lifetime-null-deref"></a>Lifetime.2: Don't dereference a possibly null pointer.
|
||||
|
||||
##### Reason
|
||||
|
||||
It is undefined behavior.
|
||||
|
||||
##### Example, bad
|
||||
|
||||
void f(int* p1)
|
||||
{
|
||||
*p1 = 42; // BAD, p1 might be null
|
||||
|
||||
int i = 0;
|
||||
int* p2 = condition() ? &i : nullptr;
|
||||
*p2 = 42; // BAD, p2 might be null
|
||||
}
|
||||
|
||||
##### Example, good
|
||||
|
||||
void f(int* p1, not_null<int*> p3)
|
||||
{
|
||||
if (p1 != nullptr) {
|
||||
*p1 = 42; // OK, must be not null in this branch
|
||||
}
|
||||
|
||||
int i = 0;
|
||||
int* p2 = condition() ? &i : nullptr;
|
||||
if (p2 != nullptr) {
|
||||
*p2 = 42; // OK, must be not null in this branch
|
||||
}
|
||||
|
||||
*p3 = 42; // OK, not_null does not need to be tested for nullness
|
||||
}
|
||||
|
||||
##### Enforcement
|
||||
|
||||
* Issue a diagnostic for any dereference of a pointer that could have been set to null along a local code path leading to the dereference. To fix: Add a null check and dereference the pointer only in a branch that has tested to ensure non-null.
|
||||
|
||||
|
||||
|
||||
### <a name="Pro-lifetime-invalid-argument"></a>Lifetime.3: Don't pass a possibly invalid pointer to a function.
|
||||
|
||||
##### Reason
|
||||
|
||||
The function cannot do anything useful with the pointer.
|
||||
|
||||
To resolve the problem, either extend the lifetime of the object the pointer is intended to refer to, or shorten the lifetime of the pointer (move the function call to before the pointed-to object's lifetime ends).
|
||||
|
||||
##### Example, bad
|
||||
|
||||
void f(int*);
|
||||
|
||||
void g()
|
||||
{
|
||||
int x = 0;
|
||||
int* p = &x;
|
||||
|
||||
if (condition()) {
|
||||
int y = 0;
|
||||
p = &y;
|
||||
} // invalidates p
|
||||
|
||||
f(p); // BAD, p might be invalid if the branch was taken
|
||||
}
|
||||
|
||||
##### Example, good
|
||||
|
||||
void f()
|
||||
{
|
||||
int x = 0;
|
||||
int* p = &x;
|
||||
|
||||
int y = 0;
|
||||
if (condition()) {
|
||||
p = &y;
|
||||
}
|
||||
|
||||
f(p); // OK, p points to x or y and both are still in scope
|
||||
}
|
||||
|
||||
##### Enforcement
|
||||
|
||||
* Issue a diagnostic for any function argument that is a pointer that could have been invalidated (could point to an object that was destroyed) along a local code path leading to the dereference. To fix: Extend the lifetime of the pointed-to object, or move the function call to before the pointed-to object's lifetime ends.
|
||||
Once completely enforced through a combination of style rules, static analysis, and library support, this profile
|
||||
|
||||
* eliminates one of the major sources of nasty errors in C++
|
||||
* eliminates a major source of potential security violations
|
||||
* improves performance by eliminating redundant "paranoia" checks
|
||||
* increases confidence in correctness of code
|
||||
* avoids undefined behavior by enforcing a key C++ language rule
|
||||
|
||||
|
||||
# <a name="S-gsl"></a>GSL: Guideline support library
|
||||
@@ -19634,7 +19657,7 @@ These types allow the user to distinguish between owning and non-owning pointers
|
||||
|
||||
These "views" are never owners.
|
||||
|
||||
References are never owners. 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. 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):
|
||||
|
||||
@@ -19662,7 +19685,7 @@ If something is not supposed to be `nullptr`, say so:
|
||||
* `not_null<T>` // `T` is usually a pointer type (e.g., `not_null<int*>` and `not_null<owner<Foo*>>`) that may not be `nullptr`.
|
||||
`T` can be any type for which `==nullptr` is meaningful.
|
||||
|
||||
* `span<T>` // `[`p`:`p+n`)`, constructor from `{p, q}` and `{p, n}`; `T` is the pointer type
|
||||
* `span<T>` // `[`p`:`p + n`)`, constructor from `{p, q}` and `{p, n}`; `T` is the pointer type
|
||||
* `span_p<T>` // `{p, predicate}` \[`p`:`q`) where `q` is the first element for which `predicate(*p)` is true
|
||||
* `string_span` // `span<char>`
|
||||
* `cstring_span` // `span<const char>`
|
||||
@@ -19699,7 +19722,7 @@ Use `not_null<zstring>` for C-style strings that cannot be `nullptr`. ??? Do we
|
||||
These assertions are currently macros (yuck!) and must appear in function definitions (only)
|
||||
pending standard committee decisions on contracts and assertion syntax.
|
||||
See [the contract proposal](http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2016/p0380r1.pdf); using the attribute syntax,
|
||||
for example, `Expects(p!=nullptr)` will become `[[expects: p!=nullptr]]`.
|
||||
for example, `Expects(p != nullptr)` will become `[[expects: p != nullptr]]`.
|
||||
|
||||
## <a name="SS-utilities"></a>GSL.util: Utilities
|
||||
|
||||
@@ -21033,7 +21056,7 @@ When is a class a container? ???
|
||||
A relatively informal definition of terms used in the guidelines
|
||||
(based of the glossary in [Programming: Principles and Practice using C++](http://www.stroustrup.com/programming.html))
|
||||
|
||||
More information on many topics about C++ can be found on the [Standard C++ Foundation](https://isocpp.org)'s site.
|
||||
More information on many topics about C++ can be found on the [Standard C++ Foundation](https://isocpp.org)'s site.
|
||||
|
||||
* *ABI*: Application Binary Interface, a specification for a specific hardware platform combined with the operating system. Contrast with API.
|
||||
* *abstract class*: a class that cannot be directly used to create objects; often used to define an interface to derived classes.
|
||||
@@ -21161,7 +21184,7 @@ In particular, an object of a regular type can be copied and the result of a cop
|
||||
* *subtype*: derived type; a type that has all the properties of a type and possibly more.
|
||||
* *supertype*: base type; a type that has a subset of the properties of a type.
|
||||
* *system*: (1) a program or a set of programs for performing a task on a computer; (2) a shorthand for "operating system", that is, the fundamental execution environment and tools for a computer.
|
||||
* *TS*: [Technical Specification](https://www.iso.org/deliverables-all.html?type=ts), A Technical Specification addresses work still under technical development, or where it is believed that there will be a future, but not immediate, possibility of agreement on an International Standard. A Technical Specification is published for immediate use, but it also provides a means to obtain feedback. The aim is that it will eventually be transformed and republished as an International Standard.
|
||||
* *TS*: [Technical Specification](https://www.iso.org/deliverables-all.html?type=ts), A Technical Specification addresses work still under technical development, or where it is believed that there will be a future, but not immediate, possibility of agreement on an International Standard. A Technical Specification is published for immediate use, but it also provides a means to obtain feedback. The aim is that it will eventually be transformed and republished as an International Standard.
|
||||
* *template*: a class or a function parameterized by one or more types or (compile-time) values; the basic C++ language construct supporting generic programming.
|
||||
* *testing*: a systematic search for errors in a program.
|
||||
* *trade-off*: the result of balancing several design and implementation criteria.
|
||||
@@ -21271,6 +21294,8 @@ Alternatively, we will decide that no change is needed and delete the entry.
|
||||
* <a name="Stroustrup14"></a>
|
||||
\[Stroustrup14]: B. Stroustrup. [A Tour of C++](http://www.stroustrup.com/Tour.html).
|
||||
Addison Wesley 2014.
|
||||
* <a name="Stroustrup15"></a>
|
||||
\[Stroustrup15]: B. Stroustrup, Herb Sutter, and G. Dos Reis: [A brief introduction to C++'s model for type- and resource-safety](https://github.com/isocpp/CppCoreGuidelines/blob/master/docs/Introduction%20to%20type%20and%20resource%20safety.pdf).
|
||||
* <a name="SuttHysl04b"></a>
|
||||
\[SuttHysl04b]: H. Sutter and J. Hyslop. "Collecting Shared Objects" (C/C++ Users Journal, 22(8), August 2004).
|
||||
* <a name="SuttAlex05"></a>
|
||||
|
||||
Reference in New Issue
Block a user