mirror of
https://github.com/isocpp/CppCoreGuidelines.git
synced 2024-03-22 13:30:58 +08:00
commit
4359ca5ae5
|
@ -853,7 +853,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):
|
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)
|
void use3(int m)
|
||||||
|
@ -1874,7 +1874,7 @@ Note: `length()` is, of course, `std::strlen()` in disguise.
|
||||||
|
|
||||||
Consider:
|
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 `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.
|
What if there are fewer than `n` elements in the array pointed to by `p`? Then, we read some probably unrelated memory.
|
||||||
|
@ -1964,13 +1964,14 @@ Having many arguments opens opportunities for confusion. Passing lots of argumen
|
||||||
|
|
||||||
The two most common reasons why functions have too many parameters are:
|
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
|
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.
|
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
|
This not only expands the parameter list, but it leads to errors because the component values
|
||||||
are no longer protected by an enforced invariant.
|
are no longer protected by an enforced invariant.
|
||||||
|
|
||||||
2. *Violating "one function, one responsibility."* The function is trying to do more than one
|
2. *Violating "one function, one responsibility."*
|
||||||
job and should probably be refactored.
|
The function is trying to do more than one job and should probably be refactored.
|
||||||
|
|
||||||
##### Example
|
##### Example
|
||||||
|
|
||||||
|
@ -2040,13 +2041,13 @@ Adjacent arguments of the same type are easily swapped by mistake.
|
||||||
|
|
||||||
Consider:
|
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.
|
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:
|
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
|
##### Exception
|
||||||
|
|
||||||
|
@ -2389,8 +2390,8 @@ Functions with complex control structures are more likely to be long and more li
|
||||||
|
|
||||||
Consider:
|
Consider:
|
||||||
|
|
||||||
double simpleFunc(double val, int flag1, int flag2)
|
double simple_func(double val, int flag1, int flag2)
|
||||||
// simpleFunc: takes a value and calculates the expected ASIC output,
|
// simple_func: takes a value and calculates the expected ASIC output,
|
||||||
// given the two mode flags.
|
// given the two mode flags.
|
||||||
{
|
{
|
||||||
double intermediate;
|
double intermediate;
|
||||||
|
@ -2433,8 +2434,8 @@ We can refactor:
|
||||||
// ???
|
// ???
|
||||||
}
|
}
|
||||||
|
|
||||||
double simpleFunc(double val, int flag1, int flag2)
|
double simple_func(double val, int flag1, int flag2)
|
||||||
// simpleFunc: takes a value and calculates the expected ASIC output,
|
// simple_func: takes a value and calculates the expected ASIC output,
|
||||||
// given the two mode flags.
|
// given the two mode flags.
|
||||||
{
|
{
|
||||||
if (flag1 > 0)
|
if (flag1 > 0)
|
||||||
|
@ -3191,7 +3192,7 @@ Informal/non-explicit ranges are a source of errors.
|
||||||
##### Note
|
##### Note
|
||||||
|
|
||||||
Ranges are extremely common in C++ code. Typically, they are implicit and their correct use is very hard to ensure.
|
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`.
|
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.
|
`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.
|
||||||
|
|
||||||
|
@ -4186,7 +4187,7 @@ For example, a derived class might be allowed to skip a run-time check because i
|
||||||
int mem(int x, int y)
|
int mem(int x, int y)
|
||||||
{
|
{
|
||||||
/* ... do something ... */
|
/* ... do something ... */
|
||||||
return do_bar(x+y); // OK: derived class can bypass check
|
return do_bar(x + y); // OK: derived class can bypass check
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -7167,7 +7168,7 @@ Without a using declaration, member functions in the derived class hide the enti
|
||||||
};
|
};
|
||||||
class D: public B {
|
class D: public B {
|
||||||
public:
|
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()
|
int main()
|
||||||
{
|
{
|
||||||
|
@ -7180,7 +7181,7 @@ Without a using declaration, member functions in the derived class hide the enti
|
||||||
|
|
||||||
class D: public B {
|
class D: public B {
|
||||||
public:
|
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)
|
using B::f; // exposes f(double)
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -10384,17 +10385,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.
|
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
|
std::string buffer; // to avoid reallocations on every loop iteration
|
||||||
for (auto& o : objects)
|
for (auto& o : objects)
|
||||||
{
|
{
|
||||||
// First part of the work.
|
// First part of the work.
|
||||||
generateFirstString(buffer, o);
|
generate_first_String(buffer, o);
|
||||||
writeToFile(buffer);
|
write_to_file(buffer);
|
||||||
|
|
||||||
// Second part of the work.
|
// Second part of the work.
|
||||||
generateSecondString(buffer, o);
|
generate_second_string(buffer, o);
|
||||||
writeToFile(buffer);
|
write_to_file(buffer);
|
||||||
|
|
||||||
// etc...
|
// etc...
|
||||||
}
|
}
|
||||||
|
@ -11060,11 +11061,11 @@ There are exceedingly clever used of this "idiom", but they are far rarer than t
|
||||||
|
|
||||||
##### Note
|
##### Note
|
||||||
|
|
||||||
Unnamed function arguments are fine.
|
Unnamed function arguments are fine.
|
||||||
|
|
||||||
##### Enforcement
|
##### 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
|
### <a name="Res-empty"></a>ES.85: Make empty statements visible
|
||||||
|
|
||||||
|
@ -12260,7 +12261,7 @@ can be surprising for many programmers.
|
||||||
##### Reason
|
##### Reason
|
||||||
|
|
||||||
Because most arithmetic is assumed to be signed;
|
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
|
##### Example
|
||||||
|
|
||||||
|
@ -12270,7 +12271,7 @@ This is even more true for mixed signed and unsigned arithmetic.
|
||||||
template<typename T, typename T2>
|
template<typename T, typename T2>
|
||||||
T subtract(T x, T2 y)
|
T subtract(T x, T2 y)
|
||||||
{
|
{
|
||||||
return x-y;
|
return x - y;
|
||||||
}
|
}
|
||||||
|
|
||||||
void test()
|
void test()
|
||||||
|
@ -12281,12 +12282,12 @@ This is even more true for mixed signed and unsigned arithmetic.
|
||||||
cout << subtract(us, 7u) << '\n'; // 4294967294
|
cout << subtract(us, 7u) << '\n'; // 4294967294
|
||||||
cout << subtract(s, 7u) << '\n'; // -2
|
cout << subtract(s, 7u) << '\n'; // -2
|
||||||
cout << subtract(us, 7) << '\n'; // 4294967294
|
cout << subtract(us, 7) << '\n'; // 4294967294
|
||||||
cout << subtract(s, us+2) << '\n'; // -2
|
cout << subtract(s, us + 2) << '\n'; // -2
|
||||||
cout << subtract(us, s+2) << '\n'; // 4294967294
|
cout << subtract(us, s + 2) << '\n'; // 4294967294
|
||||||
}
|
}
|
||||||
|
|
||||||
Here we have been very explicit about what's happening,
|
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
|
##### Exception
|
||||||
|
|
||||||
|
@ -12301,10 +12302,10 @@ The build-in array uses signed types for subscripts.
|
||||||
This makes surprises (and bugs) inevitable.
|
This makes surprises (and bugs) inevitable.
|
||||||
|
|
||||||
int a[10];
|
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);
|
vector<int> v(10);
|
||||||
// compares signed to unsigned; some compilers warn
|
// 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
|
int a2[-2]; // error: negative size
|
||||||
|
|
||||||
|
@ -12491,13 +12492,13 @@ To enable better error detection.
|
||||||
|
|
||||||
vector<int> vec {1, 2, 3, 4, 5};
|
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';
|
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';
|
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';
|
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';
|
cout << vec[i] << '\n';
|
||||||
|
|
||||||
##### Note
|
##### Note
|
||||||
|
@ -15249,7 +15250,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`,
|
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).
|
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
|
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
|
||||||
|
@ -17939,11 +17940,11 @@ The argument-type error for `bar` cannot be caught until link time because of th
|
||||||
|
|
||||||
##### Example
|
##### Example
|
||||||
|
|
||||||
#include<string>
|
#include <string>
|
||||||
#include<vector>
|
#include <vector>
|
||||||
#include<iostream>
|
#include <iostream>
|
||||||
#include<memory>
|
#include <memory>
|
||||||
#include<algorithm>
|
#include <algorithm>
|
||||||
|
|
||||||
using namespace std;
|
using namespace std;
|
||||||
|
|
||||||
|
@ -17956,7 +17957,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
|
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;
|
using namespace std;
|
||||||
|
|
||||||
int g(int x)
|
int g(int x)
|
||||||
|
@ -18093,7 +18094,7 @@ but it is not required to do so by transitively including the entire `<string>`
|
||||||
resulting in the popular beginner question "why doesn't `getline(cin,s);` work?"
|
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>`:
|
The solution is to explicitly `#include <string>`:
|
||||||
|
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
#include <string>
|
#include <string>
|
||||||
|
@ -18480,11 +18481,11 @@ Don't use C-style strings for operations that require non-trivial memory managem
|
||||||
{
|
{
|
||||||
int l1 = strlen(s1);
|
int l1 = strlen(s1);
|
||||||
int l2 = strlen(s2);
|
int l2 = strlen(s2);
|
||||||
char* p = (char*)malloc(l1+l2+2);
|
char* p = (char*) malloc(l1 + l2 + 2);
|
||||||
strcpy(p, s1, l1);
|
strcpy(p, s1, l1);
|
||||||
p[l1] = '.';
|
p[l1] = '.';
|
||||||
strcpy(p+l1+1, s2, l2);
|
strcpy(p + l1 + 1, s2, l2);
|
||||||
p[l1+l2+1] = 0;
|
p[l1 + l2 + 1] = 0;
|
||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -19375,7 +19376,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:
|
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]
|
||||||
{
|
{
|
||||||
// ...
|
// ...
|
||||||
}
|
}
|
||||||
|
@ -19630,7 +19631,7 @@ These types allow the user to distinguish between owning and non-owning pointers
|
||||||
|
|
||||||
These "views" are never owners.
|
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):
|
The names are mostly ISO standard-library style (lower case and underscore):
|
||||||
|
|
||||||
|
@ -19658,7 +19659,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`.
|
* `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.
|
`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
|
* `span_p<T>` // `{p, predicate}` \[`p`:`q`) where `q` is the first element for which `predicate(*p)` is true
|
||||||
* `string_span` // `span<char>`
|
* `string_span` // `span<char>`
|
||||||
* `cstring_span` // `span<const char>`
|
* `cstring_span` // `span<const char>`
|
||||||
|
@ -19695,7 +19696,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)
|
These assertions are currently macros (yuck!) and must appear in function definitions (only)
|
||||||
pending standard committee decisions on contracts and assertion syntax.
|
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,
|
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
|
## <a name="SS-utilities"></a>GSL.util: Utilities
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue
Block a user