mirror of
https://github.com/isocpp/CppCoreGuidelines.git
synced 2025-12-18 21:24:41 +03:00
update from master
This commit is contained in:
@@ -1,7 +1,6 @@
|
||||
# <a name="main"></a>C++ Core Guidelines
|
||||
|
||||
September 23, 2022
|
||||
|
||||
April 13, 2023
|
||||
|
||||
Editors:
|
||||
|
||||
@@ -43,7 +42,7 @@ You can [read an explanation of the scope and structure of this Guide](#S-abstra
|
||||
* [T: Templates and generic programming](#S-templates)
|
||||
* [CPL: C-style programming](#S-cpl)
|
||||
* [SF: Source files](#S-source)
|
||||
* [SL: The Standard Library](#S-stdlib)
|
||||
* [SL: The Standard Library](#sl-the-standard-library)
|
||||
|
||||
Supporting sections:
|
||||
|
||||
@@ -51,7 +50,7 @@ Supporting sections:
|
||||
* [NR: Non-Rules and myths](#S-not)
|
||||
* [RF: References](#S-references)
|
||||
* [Pro: Profiles](#S-profile)
|
||||
* [GSL: Guidelines support library](#S-gsl)
|
||||
* [GSL: Guidelines support library](#gsl-guidelines-support-library)
|
||||
* [NL: Naming and layout suggestions](#S-naming)
|
||||
* [FAQ: Answers to frequently asked questions](#S-faq)
|
||||
* [Appendix A: Libraries](#S-libraries)
|
||||
@@ -255,7 +254,7 @@ Take the time to understand the implications of a guideline rule on your program
|
||||
|
||||
These guidelines are designed according to the "subset of superset" principle ([Stroustrup05](#Stroustrup05)).
|
||||
They do not simply define a subset of C++ to be used (for reliability, safety, performance, or whatever).
|
||||
Instead, they strongly recommend the use of a few simple "extensions" ([library components](#S-gsl))
|
||||
Instead, they strongly recommend the use of a few simple "extensions" ([library components](#gsl-guidelines-support-library))
|
||||
that make the use of the most error-prone features of C++ redundant, so that they can be banned (in our set of rules).
|
||||
|
||||
The rules emphasize static type safety and resource safety.
|
||||
@@ -436,7 +435,7 @@ Recommended information sources can be found in [the references](#S-references).
|
||||
* [T: Templates and generic programming](#S-templates)
|
||||
* [CPL: C-style programming](#S-cpl)
|
||||
* [SF: Source files](#S-source)
|
||||
* [SL: The Standard Library](#S-stdlib)
|
||||
* [SL: The Standard Library](#sl-the-standard-library)
|
||||
|
||||
Supporting sections:
|
||||
|
||||
@@ -444,7 +443,7 @@ Supporting sections:
|
||||
* [NR: Non-Rules and myths](#S-not)
|
||||
* [RF: References](#S-references)
|
||||
* [Pro: Profiles](#S-profile)
|
||||
* [GSL: Guidelines support library](#S-gsl)
|
||||
* [GSL: Guidelines support library](#gsl-guidelines-support-library)
|
||||
* [NL: Naming and layout suggestions](#S-naming)
|
||||
* [FAQ: Answers to frequently asked questions](#S-faq)
|
||||
* [Appendix A: Libraries](#S-libraries)
|
||||
@@ -538,7 +537,7 @@ A well-designed library expresses intent (what is to be done, rather than just h
|
||||
|
||||
A C++ programmer should know the basics of the standard library, and use it where appropriate.
|
||||
Any programmer should know the basics of the foundation libraries of the project being worked on, and use them appropriately.
|
||||
Any programmer using these guidelines should know the [guidelines support library](#S-gsl), and use it appropriately.
|
||||
Any programmer using these guidelines should know the [guidelines support library](#gsl-guidelines-support-library), and use it appropriately.
|
||||
|
||||
##### Example
|
||||
|
||||
@@ -629,8 +628,8 @@ The last variant makes it clear that we are not interested in the order in which
|
||||
|
||||
A programmer should be familiar with
|
||||
|
||||
* [The guidelines support library](#S-gsl)
|
||||
* [The ISO C++ Standard Library](#S-stdlib)
|
||||
* [The guidelines support library](#gsl-guidelines-support-library)
|
||||
* [The ISO C++ Standard Library](#sl-the-standard-library)
|
||||
* Whatever foundation libraries are used for the current project(s)
|
||||
|
||||
##### Note
|
||||
@@ -645,7 +644,9 @@ Some language constructs express intent better than others.
|
||||
|
||||
If two `int`s are meant to be the coordinates of a 2D point, say so:
|
||||
|
||||
draw_line(int, int, int, int); // obscure
|
||||
draw_line(int, int, int, int); // obscure: (x1,y1,x2,y2)? (x,y,h,w)? ...?
|
||||
// need to look up documentation to know
|
||||
|
||||
draw_line(Point, Point); // clearer
|
||||
|
||||
##### Enforcement
|
||||
@@ -1007,7 +1008,7 @@ Combine this with enforcement of [the type and bounds profiles](#SS-force) and y
|
||||
|
||||
* Look at pointers: Classify them into non-owners (the default) and owners.
|
||||
Where feasible, replace owners with standard-library resource handles (as in the example above).
|
||||
Alternatively, mark an owner as such using `owner` from [the GSL](#S-gsl).
|
||||
Alternatively, mark an owner as such using `owner` from [the GSL](#gsl-guidelines-support-library).
|
||||
* Look for naked `new` and `delete`
|
||||
* Look for known resource allocating functions returning raw pointers (such as `fopen`, `malloc`, and `strdup`)
|
||||
|
||||
@@ -1071,7 +1072,7 @@ There are several more performance bugs and gratuitous complication.
|
||||
}
|
||||
|
||||
This is actually an example from production code.
|
||||
We can see that in our condition we have `i < strlen(s)`. This expression will be evaluated on every iteration of the loop, which means that `strlen` must walk through string every loop to discover its length. While the string contents are changing, it's assumed that `toLower` will not affect the length of the string, so it's better to cache the length outside the loop and not incur that cost each iteration.
|
||||
We can see that in our condition we have `i < strlen(s)`. This expression will be evaluated on every iteration of the loop, which means that `strlen` must walk through string every loop to discover its length. While the string contents are changing, it's assumed that `tolower` will not affect the length of the string, so it's better to cache the length outside the loop and not incur that cost each iteration.
|
||||
|
||||
##### Note
|
||||
|
||||
@@ -1201,8 +1202,8 @@ You need a reason not to use the standard library (or whatever foundational libr
|
||||
|
||||
By default use
|
||||
|
||||
* The [ISO C++ Standard Library](#S-stdlib)
|
||||
* The [Guidelines Support Library](#S-gsl)
|
||||
* The [ISO C++ Standard Library](#sl-the-standard-library)
|
||||
* The [Guidelines Support Library](#gsl-guidelines-support-library)
|
||||
|
||||
##### Note
|
||||
|
||||
@@ -1274,7 +1275,7 @@ The use of a non-local control is potentially confusing, but controls only imple
|
||||
|
||||
Reporting through non-local variables (e.g., `errno`) is easily ignored. For example:
|
||||
|
||||
// don't: no test of printf's return value
|
||||
// don't: no test of fprintf's return value
|
||||
fprintf(connection, "logging: %d %d %d\n", x, y, s);
|
||||
|
||||
What if the connection goes down so that no logging output is produced? See I.???.
|
||||
@@ -1561,7 +1562,7 @@ Some preconditions can be expressed as assertions. For example:
|
||||
|
||||
Ideally, that `Expects(x >= 0)` should be part of the interface of `sqrt()` but that's not easily done. For now, we place it in the definition (function body).
|
||||
|
||||
**References**: `Expects()` is described in [GSL](#S-gsl).
|
||||
**References**: `Expects()` is described in [GSL](#gsl-guidelines-support-library).
|
||||
|
||||
##### Note
|
||||
|
||||
@@ -1869,7 +1870,7 @@ However, that is less elegant and often less efficient than returning the object
|
||||
so use smart pointers only if reference semantics are needed.
|
||||
|
||||
**Alternative**: Sometimes older code can't be modified because of ABI compatibility requirements or lack of resources.
|
||||
In that case, mark owning pointers using `owner` from the [guidelines support library](#S-gsl):
|
||||
In that case, mark owning pointers using `owner` from the [guidelines support library](#gsl-guidelines-support-library):
|
||||
|
||||
owner<X*> compute(args) // It is now clear that ownership is transferred
|
||||
{
|
||||
@@ -1919,7 +1920,7 @@ By stating the intent in source, implementers and tools can provide better diagn
|
||||
|
||||
##### Note
|
||||
|
||||
`not_null` is defined in the [guidelines support library](#S-gsl).
|
||||
`not_null` is defined in the [guidelines support library](#gsl-guidelines-support-library).
|
||||
|
||||
##### Note
|
||||
|
||||
@@ -1985,7 +1986,7 @@ This `draw2()` passes the same amount of information to `draw()`, but makes the
|
||||
##### Exception
|
||||
|
||||
Use `zstring` and `czstring` to represent C-style, zero-terminated strings.
|
||||
But when doing so, use `std::string_view` or `span<char>` from the [GSL](#S-gsl) to prevent range errors.
|
||||
But when doing so, use `std::string_view` or `span<char>` from the [GSL](#gsl-guidelines-support-library) to prevent range errors.
|
||||
|
||||
##### Enforcement
|
||||
|
||||
@@ -2377,7 +2378,8 @@ Parameter passing semantic rules:
|
||||
* [F.45: Don't return a `T&&`](#Rf-return-ref-ref)
|
||||
* [F.46: `int` is the return type for `main()`](#Rf-main)
|
||||
* [F.47: Return `T&` from assignment operators](#Rf-assignment-op)
|
||||
* [F.48: Don't `return std::move(local)`](#Rf-return-move-local)
|
||||
* [F.48: Don't return `std::move(local)`](#Rf-return-move-local)
|
||||
* [F.49: Don't return `const T`](#Rf-return-const)
|
||||
|
||||
Other function rules:
|
||||
|
||||
@@ -2385,7 +2387,7 @@ Other function rules:
|
||||
* [F.51: Where there is a choice, prefer default arguments over overloading](#Rf-default-args)
|
||||
* [F.52: Prefer capturing by reference in lambdas that will be used locally, including passed to algorithms](#Rf-reference-capture)
|
||||
* [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.54: When writing a lambda that captures `this` or any class data member, don't use `[=]` default capture](#Rf-this-capture)
|
||||
* [F.55: Don't use `va_arg` arguments](#F-varargs)
|
||||
* [F.56: Avoid unnecessary condition nesting](#F-nesting)
|
||||
|
||||
@@ -2850,7 +2852,7 @@ Suppression of unused parameter warnings.
|
||||
|
||||
##### Note
|
||||
|
||||
Allowing parameters to be unnamed was introduced in the early 1980 to address this problem.
|
||||
Allowing parameters to be unnamed was introduced in the early 1980s to address this problem.
|
||||
|
||||
If parameters are conditionally unused, declare them with the `[[maybe_unused]]` attribute.
|
||||
For example:
|
||||
@@ -2908,7 +2910,7 @@ There is a useful function lurking here (case insensitive string comparison), as
|
||||
}
|
||||
|
||||
auto x = find_if(vr.begin(), vr.end(),
|
||||
[&](Rec& r) { compare_insensitive(r.name, n); }
|
||||
[&](Rec& r) { return compare_insensitive(r.name, n); }
|
||||
);
|
||||
|
||||
Or maybe (if you prefer to avoid the implicit name binding to n):
|
||||
@@ -2978,6 +2980,11 @@ Use the advanced techniques only after demonstrating need, and document that nee
|
||||
|
||||
For passing sequences of characters see [String](#SS-string).
|
||||
|
||||
##### Exception
|
||||
|
||||
To express shared ownership using `shared_ptr` types, rather than following guidelines F.16-21,
|
||||
follow [R.34](#Rr-sharedptrparam-owner), [R.35](#Rr-sharedptrparam), and [R.36](#Rr-sharedptrparam-const).
|
||||
|
||||
### <a name="Rf-in"></a>F.16: For "in" parameters, pass cheaply-copied types by value and others by reference to `const`
|
||||
|
||||
##### Reason
|
||||
@@ -3026,9 +3033,14 @@ If you need the notion of an optional value, use a pointer, `std::optional`, or
|
||||
|
||||
* (Simple) ((Foundation)) Warn when a parameter being passed by value has a size greater than `2 * sizeof(void*)`.
|
||||
Suggest using a reference to `const` instead.
|
||||
* (Simple) ((Foundation)) Warn when a parameter passed by reference to `const` has a size less than `2 * sizeof(void*)`. Suggest passing by value instead.
|
||||
* (Simple) ((Foundation)) Warn when a parameter passed by reference to `const` has a size less or equal than `2 * sizeof(void*)`. Suggest passing by value instead.
|
||||
* (Simple) ((Foundation)) Warn when a parameter passed by reference to `const` is `move`d.
|
||||
|
||||
##### Exception
|
||||
|
||||
To express shared ownership using `shared_ptr` types, follow [R.34](#Rr-sharedptrparam-owner) or [R.36](#Rr-sharedptrparam-const),
|
||||
depending on whether or not the function unconditionally takes a reference to the argument.
|
||||
|
||||
### <a name="Rf-inout"></a>F.17: For "in-out" parameters, pass by reference to non-`const`
|
||||
|
||||
##### Reason
|
||||
@@ -3107,6 +3119,10 @@ For example:
|
||||
// use p ... possibly std::move(p) onward somewhere else
|
||||
} // p gets destroyed
|
||||
|
||||
##### Exception
|
||||
|
||||
If the "will-move-from" parameter is a `shared_ptr` follow [R.34](#Rr-sharedptrparam-owner) and pass the `shared_ptr` by value.
|
||||
|
||||
##### Enforcement
|
||||
|
||||
* Flag all `X&&` parameters (where `X` is not a template type parameter name) where the function body uses them without `std::move`.
|
||||
@@ -3123,17 +3139,29 @@ In that case, and only that case, make the parameter `TP&&` where `TP` is a temp
|
||||
|
||||
##### Example
|
||||
|
||||
Usually you forward the entire parameter (or parameter pack, using `...`) exactly once on every static control flow path:
|
||||
|
||||
template<class F, class... Args>
|
||||
inline auto invoke(F f, Args&&... args)
|
||||
{
|
||||
return f(forward<Args>(args)...);
|
||||
}
|
||||
|
||||
??? calls ???
|
||||
##### Example
|
||||
|
||||
Sometimes you may forward a composite parameter piecewise, each subobject once on every static control flow path:
|
||||
|
||||
template<class PairLike>
|
||||
inline auto test(PairLike&&... pairlike)
|
||||
{
|
||||
// ...
|
||||
f1(some, args, and, forward<PairLike>(pairlike).first); // forward .first
|
||||
f2(and, forward<PairLike>(pairlike).second, in, another, call); // forward .second
|
||||
}
|
||||
|
||||
##### Enforcement
|
||||
|
||||
* Flag a function that takes a `TP&&` parameter (where `TP` is a template type parameter name) and does anything with it other than `std::forward`ing it exactly once on every static path.
|
||||
* Flag a function that takes a `TP&&` parameter (where `TP` is a template type parameter name) and does anything with it other than `std::forward`ing it exactly once on every static path, or `std::forward`ing it more than once but qualified with a different data member exactly once on every static path.
|
||||
|
||||
### <a name="Rf-out"></a>F.20: For "out" output values, prefer return values to output parameters
|
||||
|
||||
@@ -3157,25 +3185,6 @@ If you have multiple values to return, [use a tuple](#Rf-out-multi) or similar m
|
||||
|
||||
A `struct` of many (individually cheap-to-move) elements might be in aggregate expensive to move.
|
||||
|
||||
##### Note
|
||||
|
||||
It is not recommended to return a `const` value.
|
||||
Such older advice is now obsolete; it does not add value, and it interferes with move semantics.
|
||||
|
||||
const vector<int> fct(); // bad: that "const" is more trouble than it is worth
|
||||
|
||||
void g(vector<int>& vx)
|
||||
{
|
||||
// ...
|
||||
fct() = vx; // prevented by the "const"
|
||||
// ...
|
||||
vx = fct(); // expensive copy: move semantics suppressed by the "const"
|
||||
// ...
|
||||
}
|
||||
|
||||
The argument for adding `const` to a return value is that it prevents (very rare) accidental access to a temporary.
|
||||
The argument against is that it prevents (very frequent) use of move semantics.
|
||||
|
||||
##### Exceptions
|
||||
|
||||
* For non-concrete types, such as types in an inheritance hierarchy, return the object by `unique_ptr` or `shared_ptr`.
|
||||
@@ -3218,7 +3227,6 @@ The return value optimization doesn't handle the assignment case, but the move a
|
||||
##### Enforcement
|
||||
|
||||
* Flag reference to non-`const` parameters that are not read before being written to and are a type that could be cheaply returned; they should be "out" return values.
|
||||
* Flag returning a `const` value. To fix: Remove `const` to return a non-`const` value instead.
|
||||
|
||||
### <a name="Rf-out-multi"></a>F.21: To return multiple "out" values, prefer returning a struct or tuple
|
||||
|
||||
@@ -3271,14 +3279,14 @@ In such cases, passing the object by reference [`T&`](#Rf-inout) is usually the
|
||||
Explicitly passing an in-out parameter back out again as a return value is often not necessary.
|
||||
For example:
|
||||
|
||||
istream& operator>>(istream& is, string& s); // much like std::operator>>()
|
||||
istream& operator>>(istream& in, string& s); // much like std::operator>>()
|
||||
|
||||
for (string s; cin >> s; ) {
|
||||
for (string s; in >> s; ) {
|
||||
// do something with line
|
||||
}
|
||||
|
||||
Here, both `s` and `cin` are used as in-out parameters.
|
||||
We pass `cin` by (non-`const`) reference to be able to manipulate its state.
|
||||
Here, both `s` and `in` are used as in-out parameters.
|
||||
We pass `in` by (non-`const`) reference to be able to manipulate its state.
|
||||
We pass `s` to avoid repeated allocations.
|
||||
By reusing `s` (passed by reference), we allocate new memory only when we need to expand `s`'s capacity.
|
||||
This technique is sometimes called the "caller-allocated out" pattern and is particularly useful for types,
|
||||
@@ -3286,11 +3294,11 @@ such as `string` and `vector`, that needs to do free store allocations.
|
||||
|
||||
To compare, if we passed out all values as return values, we would something like this:
|
||||
|
||||
pair<istream&, string> get_string(istream& is) // not recommended
|
||||
pair<istream&, string> get_string(istream& in) // not recommended
|
||||
{
|
||||
string s;
|
||||
is >> s;
|
||||
return {is, move(s)};
|
||||
in >> s;
|
||||
return {in, move(s)};
|
||||
}
|
||||
|
||||
for (auto p = get_string(cin); p.first; ) {
|
||||
@@ -3432,7 +3440,7 @@ better
|
||||
|
||||
**Also**: Assume that a `T*` obtained from a smart pointer to `T` (e.g., `unique_ptr<T>`) points to a single element.
|
||||
|
||||
**See also**: [Support library](#S-gsl)
|
||||
**See also**: [Support library](#gsl-guidelines-support-library)
|
||||
|
||||
**See also**: [Do not pass an array as a single pointer](#Ri-array)
|
||||
|
||||
@@ -3524,7 +3532,7 @@ A `span<T>` object does not own its elements and is so small that it can be pass
|
||||
|
||||
Passing a `span` object as an argument is exactly as efficient as passing a pair of pointer arguments or passing a pointer and an integer count.
|
||||
|
||||
**See also**: [Support library](#S-gsl)
|
||||
**See also**: [Support library](#gsl-guidelines-support-library)
|
||||
|
||||
##### Enforcement
|
||||
|
||||
@@ -3557,7 +3565,7 @@ When I call `length(s)` should I check if `s` is `nullptr` first? Should the imp
|
||||
|
||||
`zstring` does not represent ownership.
|
||||
|
||||
**See also**: [Support library](#S-gsl)
|
||||
**See also**: [Support library](#gsl-guidelines-support-library)
|
||||
|
||||
### <a name="Rf-unique_ptr"></a>F.26: Use a `unique_ptr<T>` to transfer ownership where a pointer is needed
|
||||
|
||||
@@ -3910,7 +3918,6 @@ This was primarily to avoid code of the form `(a = b) = c` -- such code is not c
|
||||
This should be enforced by tooling by checking the return type (and return
|
||||
value) of any assignment operator.
|
||||
|
||||
|
||||
### <a name="Rf-return-move-local"></a>F.48: Don't `return std::move(local)`
|
||||
|
||||
##### Reason
|
||||
@@ -3937,6 +3944,35 @@ With guaranteed copy elision, it is now almost always a pessimization to express
|
||||
|
||||
This should be enforced by tooling by checking the return expression .
|
||||
|
||||
### <a name="Rf-return-const"></a>F.49: Don't return `const T`
|
||||
|
||||
##### Reason
|
||||
|
||||
It is not recommended to return a `const` value.
|
||||
Such older advice is now obsolete; it does not add value, and it interferes with move semantics.
|
||||
|
||||
##### Example
|
||||
|
||||
const vector<int> fct(); // bad: that "const" is more trouble than it is worth
|
||||
|
||||
void g(vector<int>& vx)
|
||||
{
|
||||
// ...
|
||||
fct() = vx; // prevented by the "const"
|
||||
// ...
|
||||
vx = fct(); // expensive copy: move semantics suppressed by the "const"
|
||||
// ...
|
||||
}
|
||||
|
||||
The argument for adding `const` to a return value is that it prevents (very rare) accidental access to a temporary.
|
||||
The argument against is that it prevents (very frequent) use of move semantics.
|
||||
|
||||
**See also**: [F.20, the general item about "out" output values](#Rf-out)
|
||||
|
||||
##### Enforcement
|
||||
|
||||
* Flag returning a `const` value. To fix: Remove `const` to return a non-`const` value instead.
|
||||
|
||||
|
||||
### <a name="Rf-capture-vs-overload"></a>F.50: Use a lambda when a function won't do (to capture local variables, or to write a local function)
|
||||
|
||||
@@ -4073,12 +4109,18 @@ Pointers and references to locals shouldn't outlive their scope. Lambdas that ca
|
||||
// always be available for the call.
|
||||
thread_pool.queue_work([=] { process(local); });
|
||||
|
||||
##### Note
|
||||
|
||||
If a non-local pointer must be captured, consider using `unique_ptr`; this handles both lifetime and synchronization.
|
||||
|
||||
If the `this` pointer must be captured, consider using `[*this]` capture, which creates a copy of the entire object.
|
||||
|
||||
##### Enforcement
|
||||
|
||||
* (Simple) Warn when capture-list contains a reference to a locally declared variable
|
||||
* (Complex) Flag when capture-list contains a reference to a locally declared variable and the lambda is passed to a non-`const` and non-local context
|
||||
|
||||
### <a name="Rf-this-capture"></a>F.54: If you capture `this`, capture all variables explicitly (no default capture)
|
||||
### <a name="Rf-this-capture"></a>F.54: When writing a lambda that captures `this` or any class data member, don't use `[=]` default capture
|
||||
|
||||
##### Reason
|
||||
|
||||
@@ -4114,11 +4156,11 @@ It's confusing. Writing `[=]` in a member function appears to capture by value,
|
||||
|
||||
##### Note
|
||||
|
||||
This is under active discussion in standardization, and might be addressed in a future version of the standard by adding a new capture mode or possibly adjusting the meaning of `[=]`. For now, just be explicit.
|
||||
If you intend to capture a copy of all class data members, consider C++17 `[*this]`.
|
||||
|
||||
##### Enforcement
|
||||
|
||||
* Flag any lambda capture-list that specifies a default capture and also captures `this` (whether explicitly or via default capture)
|
||||
* Flag any lambda capture-list that specifies a capture-default of `[=]` and also captures `this` (whether explicitly or via the default capture and a use of `this` in the body)
|
||||
|
||||
### <a name="F-varargs"></a>F.55: Don't use `va_arg` arguments
|
||||
|
||||
@@ -4611,7 +4653,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)
|
||||
* [C.12: Don't make data members `const` or references in a copyable or movable type](#Rc-constref)
|
||||
|
||||
|
||||
### <a name="Rc-concrete"></a>C.10: Prefer concrete types over class hierarchies
|
||||
@@ -4708,11 +4750,11 @@ so they can't be regular; instead, they tend to be move-only.
|
||||
???
|
||||
|
||||
|
||||
### <a name="Rc-constref"></a>C.12: Don't make data members `const` or references
|
||||
### <a name="Rc-constref"></a>C.12: Don't make data members `const` or references in a copyable or movable type
|
||||
|
||||
##### Reason
|
||||
|
||||
They are not useful, and make types difficult to use by making them either uncopyable or partially uncopyable for subtle reasons.
|
||||
`const` and reference data members are not useful in a copyable or movable type, and make such types difficult to use by making them at least partly uncopyable/unmovable for subtle reasons.
|
||||
|
||||
##### Example; bad
|
||||
|
||||
@@ -4730,7 +4772,7 @@ If you need a member to point to something, use a pointer (raw or smart, and `gs
|
||||
|
||||
##### Enforcement
|
||||
|
||||
Flag a data member that is `const`, `&`, or `&&`.
|
||||
Flag a data member that is `const`, `&`, or `&&` in a type that has any copy or move operation.
|
||||
|
||||
|
||||
|
||||
@@ -4898,6 +4940,7 @@ defined as defaulted.
|
||||
|
||||
class AbstractBase {
|
||||
public:
|
||||
virtual void foo() = 0; // at least one abstract method to make the class abstract
|
||||
virtual ~AbstractBase() = default;
|
||||
// ...
|
||||
};
|
||||
@@ -4905,15 +4948,15 @@ defined as defaulted.
|
||||
To prevent slicing as per [C.67](#Rc-copy-virtual),
|
||||
make the copy and move operations protected or `=delete`d, and add a `clone`:
|
||||
|
||||
class ClonableBase {
|
||||
class CloneableBase {
|
||||
public:
|
||||
virtual unique_ptr<ClonableBase> clone() const;
|
||||
virtual ~ClonableBase() = default;
|
||||
virtual unique_ptr<CloneableBase> clone() const;
|
||||
virtual ~CloneableBase() = default;
|
||||
CloneableBase() = default;
|
||||
ClonableBase(const ClonableBase&) = delete;
|
||||
ClonableBase& operator=(const ClonableBase&) = delete;
|
||||
ClonableBase(ClonableBase&&) = delete;
|
||||
ClonableBase& operator=(ClonableBase&&) = delete;
|
||||
CloneableBase(const CloneableBase&) = delete;
|
||||
CloneableBase& operator=(const CloneableBase&) = delete;
|
||||
CloneableBase(CloneableBase&&) = delete;
|
||||
CloneableBase& operator=(CloneableBase&&) = delete;
|
||||
// ... other constructors and functions ...
|
||||
};
|
||||
|
||||
@@ -5563,7 +5606,7 @@ A default constructor often simplifies the task of defining a suitable [moved-fr
|
||||
};
|
||||
|
||||
vector<Date> vd1(1000); // default Date needed here
|
||||
vector<Date> vd2(1000, Date{Month::October, 7, 1885}); // alternative
|
||||
vector<Date> vd2(1000, Date{7, Month::October, 1885}); // alternative
|
||||
|
||||
The default constructor is only auto-generated if there is no user-declared constructor, hence it's impossible to initialize the vector `vd1` in the example above.
|
||||
The absence of a default value can cause surprises for users and complicate its use, so if one can be reasonably defined, it should be.
|
||||
@@ -6373,8 +6416,8 @@ Here is a way to move a pointer without a test (imagine it as code in the implem
|
||||
// move from other.ptr to this->ptr
|
||||
T* temp = other.ptr;
|
||||
other.ptr = nullptr;
|
||||
delete ptr;
|
||||
ptr = temp;
|
||||
delete ptr; // in self-move, this->ptr is also null; delete is a no-op
|
||||
ptr = temp; // in self-move, the original ptr is restored
|
||||
|
||||
##### Enforcement
|
||||
|
||||
@@ -7452,7 +7495,7 @@ Problems:
|
||||
and all classes derived from `Shape` and all code using `Shape` will need to be reviewed, possibly changed, and probably recompiled.
|
||||
|
||||
The implementation of `Shape::move()` is an example of implementation inheritance:
|
||||
we have defined `move()` once and for all for all derived classes.
|
||||
we have defined `move()` once and for all, for all derived classes.
|
||||
The more code there is in such base class member function implementations and the more data is shared by placing it in the base,
|
||||
the more benefits we gain - and the less stable the hierarchy is.
|
||||
|
||||
@@ -9027,9 +9070,13 @@ Instead use an `enum`:
|
||||
|
||||
We used an `enum class` to avoid name clashes.
|
||||
|
||||
##### Note
|
||||
|
||||
Also consider `constexpr` and `const inline` variables.
|
||||
|
||||
##### Enforcement
|
||||
|
||||
Flag macros that define integer values.
|
||||
Flag macros that define integer values. Use `enum` or `const inline` or another non-macro alternative instead.
|
||||
|
||||
|
||||
### <a name="Renum-set"></a>Enum.2: Use enumerations to represent sets of related named constants
|
||||
@@ -9200,7 +9247,7 @@ The default is the easiest to read and write.
|
||||
|
||||
##### Note
|
||||
|
||||
Specifying the underlying type is necessary in forward declarations of enumerations:
|
||||
Specifying the underlying type is necessary to forward-declare an enum or enum class:
|
||||
|
||||
enum Flags : char;
|
||||
|
||||
@@ -9210,6 +9257,9 @@ Specifying the underlying type is necessary in forward declarations of enumerati
|
||||
|
||||
enum Flags : char { /* ... */ };
|
||||
|
||||
or to ensure that values of that type have a specified bit-precision:
|
||||
|
||||
enum Bitboard : uint64_t { /* ... */ };
|
||||
|
||||
##### Enforcement
|
||||
|
||||
@@ -9548,8 +9598,30 @@ Instead, use a local variable:
|
||||
|
||||
##### Enforcement
|
||||
|
||||
* (Moderate) Warn if an object is allocated and then deallocated on all paths within a function. Suggest it should be a local `auto` stack object instead.
|
||||
* (Simple) Warn if a local `Unique_pointer` or `Shared_pointer` is not moved, copied, reassigned or `reset` before its lifetime ends.
|
||||
* (Moderate) Warn if an object is allocated and then deallocated on all paths within a function. Suggest it should be a local stack object instead.
|
||||
* (Simple) Warn if a local `Unique_pointer` or `Shared_pointer` that is not moved, copied, reassigned or `reset` before its lifetime ends is not declared `const`.
|
||||
Exception: Do not produce such a warning on a local `Unique_pointer` to an unbounded array. (See below.)
|
||||
|
||||
##### Exception
|
||||
|
||||
It is OK to create a local `const unique_ptr<T[]>` to a heap-allocated buffer, as this is a valid way to represent a scoped dynamic array.
|
||||
|
||||
##### Example
|
||||
|
||||
A valid use case for a local `const unique_ptr<T[]>` variable:
|
||||
|
||||
int get_median_value(const std::list<int>& integers)
|
||||
{
|
||||
const auto size = integers.size();
|
||||
|
||||
// OK: declaring a local unique_ptr<T[]>.
|
||||
const auto local_buffer = std::make_unique_for_overwrite<int[]>(size);
|
||||
|
||||
std::copy_n(begin(integers), size, local_buffer.get());
|
||||
std::nth_element(local_buffer.get(), local_buffer.get() + size/2, local_buffer.get() + size);
|
||||
|
||||
return local_buffer[size/2];
|
||||
}
|
||||
|
||||
### <a name="Rr-global"></a>R.6: Avoid non-`const` global variables
|
||||
|
||||
@@ -9749,19 +9821,17 @@ Consider:
|
||||
|
||||
void f()
|
||||
{
|
||||
X x;
|
||||
X* p1 { new X }; // see also ???
|
||||
unique_ptr<X> p2 { new X }; // unique ownership; see also ???
|
||||
shared_ptr<X> p3 { new X }; // shared ownership; see also ???
|
||||
auto p4 = make_unique<X>(); // unique_ownership, preferable to the explicit use "new"
|
||||
auto p5 = make_shared<X>(); // shared ownership, preferable to the explicit use "new"
|
||||
X* p1 { new X }; // bad, p1 will leak
|
||||
auto p2 = make_unique<X>(); // good, unique ownership
|
||||
auto p3 = make_shared<X>(); // good, shared ownership
|
||||
}
|
||||
|
||||
This will leak the object used to initialize `p1` (only).
|
||||
|
||||
##### Enforcement
|
||||
|
||||
(Simple) Warn if the return value of `new` or a function call with return value of pointer type is assigned to a raw pointer.
|
||||
* (Simple) Warn if the return value of `new` is assigned to a raw pointer.
|
||||
* (Simple) Warn if the result of a function returning a raw owning pointer is assigned to a raw pointer.
|
||||
|
||||
### <a name="Rr-unique"></a>R.21: Prefer `unique_ptr` over `shared_ptr` unless you need to share ownership
|
||||
|
||||
@@ -10204,7 +10274,7 @@ Large parts of the standard library rely on dynamic allocation (free store). The
|
||||
|
||||
##### Enforcement
|
||||
|
||||
Not easy. ??? Look for messy loops, nested loops, long functions, absence of function calls, lack of use of non-built-in types. Cyclomatic complexity?
|
||||
Not easy. ??? Look for messy loops, nested loops, long functions, absence of function calls, lack of use of built-in types. Cyclomatic complexity?
|
||||
|
||||
### <a name="Res-abstr"></a>ES.2: Prefer suitable abstractions to direct use of language features
|
||||
|
||||
@@ -10233,7 +10303,7 @@ The more traditional and lower-level near-equivalent is longer, messier, harder
|
||||
is.read(s, maxstring);
|
||||
res[elemcount++] = s;
|
||||
}
|
||||
nread = &elemcount;
|
||||
*nread = elemcount;
|
||||
return res;
|
||||
}
|
||||
|
||||
@@ -10241,7 +10311,7 @@ Once the checking for overflow and error handling has been added that code gets
|
||||
|
||||
##### Enforcement
|
||||
|
||||
Not easy. ??? Look for messy loops, nested loops, long functions, absence of function calls, lack of use of non-built-in types. Cyclomatic complexity?
|
||||
Not easy. ??? Look for messy loops, nested loops, long functions, absence of function calls, lack of use of built-in types. Cyclomatic complexity?
|
||||
|
||||
### <a name="Res-DRY"></a>ES.3: Don't repeat yourself, avoid redundant code
|
||||
|
||||
@@ -11104,11 +11174,12 @@ increases readability, and it has zero or near zero run-time cost.
|
||||
// ... no assignment to p2 ...
|
||||
vector<int> v(7);
|
||||
v.at(7) = 0; // exception thrown
|
||||
delete p2; // too late to prevent leaks
|
||||
// ...
|
||||
}
|
||||
|
||||
If `leak == true` the object pointed to by `p2` is leaked and the object pointed to by `p1` is not.
|
||||
The same is the case when `at()` throws.
|
||||
The same is the case when `at()` throws. In both cases, the `delete p2` statement is not reached.
|
||||
|
||||
##### Enforcement
|
||||
|
||||
@@ -12360,7 +12431,7 @@ The language already knows that a returned value is a temporary object that can
|
||||
|
||||
* Flag use of `std::move(x)` where `x` is an rvalue or the language will already treat it as an rvalue, including `return std::move(local_variable);` and `std::move(f())` on a function that returns by value.
|
||||
* Flag functions taking an `S&&` parameter if there is no `const S&` overload to take care of lvalues.
|
||||
* Flag a `std::move`s argument passed to a parameter, except when the parameter type is an `X&&` rvalue reference or the type is move-only and the parameter is passed by value.
|
||||
* Flag a `std::move`d argument passed to a parameter, except when the parameter type is an `X&&` rvalue reference or the type is move-only and the parameter is passed by value.
|
||||
* Flag when `std::move` is applied to a forwarding reference (`T&&` where `T` is a template parameter type). Use `std::forward` instead.
|
||||
* Flag when `std::move` is applied to other than an rvalue reference to non-const. (More general case of the previous rule to cover the non-forwarding cases.)
|
||||
* Flag when `std::forward` is applied to an rvalue reference (`X&&` where `X` is a non-template parameter type). Use `std::move` instead.
|
||||
@@ -13166,11 +13237,11 @@ What looks to a human like a variable without a name is to the compiler a statem
|
||||
|
||||
void f()
|
||||
{
|
||||
lock<mutex>{mx}; // Bad
|
||||
lock_guard<mutex>{mx}; // Bad
|
||||
// ...
|
||||
}
|
||||
|
||||
This declares an unnamed `lock` object that immediately goes out of scope at the point of the semicolon.
|
||||
This declares an unnamed `lock_guard` object that immediately goes out of scope at the point of the semicolon.
|
||||
This is not an uncommon mistake.
|
||||
In particular, this particular example can lead to hard-to find race conditions.
|
||||
|
||||
@@ -13636,7 +13707,7 @@ To avoid the pitfalls with `auto` and `int`.
|
||||
|
||||
##### Note
|
||||
|
||||
The built-in array uses signed subscripts.
|
||||
The built-in array allows signed subscripts.
|
||||
The standard-library containers use unsigned subscripts.
|
||||
Thus, no perfect and fully compatible solution is possible (unless and until the standard-library containers change to use signed subscripts someday in the future).
|
||||
Given the known problems with unsigned and signed/unsigned mixtures, better stick to (signed) integers of a sufficient size, which is guaranteed by `gsl::index`.
|
||||
@@ -13851,7 +13922,7 @@ In this, the `sort` interfaces shown here still have a weakness:
|
||||
They implicitly rely on the element type having less-than (`<`) defined.
|
||||
To complete the interface, we need a second version that accepts a comparison criterion:
|
||||
|
||||
// compare elements of c using p
|
||||
// compare elements of c using r
|
||||
template<random_access_range R, class C> requires sortable<R, C>
|
||||
void sort(R&& r, C c);
|
||||
|
||||
@@ -13884,7 +13955,7 @@ The ideal is zero-overhead generalization.
|
||||
* Libraries:
|
||||
Use libraries with good interfaces.
|
||||
If no library is available build one yourself and imitate the interface style from a good library.
|
||||
The [standard library](#S-stdlib) is a good first place to look for inspiration.
|
||||
The [standard library](#sl-the-standard-library) is a good first place to look for inspiration.
|
||||
* Isolation:
|
||||
Isolate your code from messy and/or old-style code by providing an interface of your choosing to it.
|
||||
This is sometimes called "providing a wrapper" for the useful/necessary but messy code.
|
||||
@@ -13934,7 +14005,7 @@ Once your first initial implementation is complete, review it; once you deploy i
|
||||
##### Note
|
||||
|
||||
A need for efficiency does not imply a need for [low-level code](#Rper-low).
|
||||
High-level code does not imply slow or bloated.
|
||||
High-level code isn't necessarily slow or bloated.
|
||||
|
||||
##### Note
|
||||
|
||||
@@ -13959,7 +14030,7 @@ One question that can be useful is
|
||||
##### Note
|
||||
|
||||
This rule does not contradict the [Don't optimize prematurely](#Rper-Knuth) rule.
|
||||
It complements it encouraging developers enable later - appropriate and non-premature - optimization, if and where needed.
|
||||
It complements it, encouraging developers to enable later - appropriate and non-premature - optimization, if and where needed.
|
||||
|
||||
##### Enforcement
|
||||
|
||||
@@ -14040,12 +14111,12 @@ There are similar techniques for selecting the optimal function to call.
|
||||
|
||||
##### Note
|
||||
|
||||
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,
|
||||
The ideal is *not* to try to 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.
|
||||
It is even possible to slow down code by compile-time computation.
|
||||
This is admittedly rare, but by factoring out a general computation into separate optimal sub-calculations it is possible to render the instruction cache less effective.
|
||||
This is admittedly rare, but by factoring out a general computation into separate optimal sub-calculations, it is possible to render the instruction cache less effective.
|
||||
|
||||
##### Enforcement
|
||||
|
||||
@@ -14093,7 +14164,7 @@ Performance is typically dominated by memory access times.
|
||||
|
||||
##### Reason
|
||||
|
||||
Performance is very sensitive to cache performance and cache algorithms favor simple (usually linear) access to adjacent data.
|
||||
Performance is very sensitive to cache performance, and cache algorithms favor simple (usually linear) access to adjacent data.
|
||||
|
||||
##### Example
|
||||
|
||||
@@ -15120,15 +15191,26 @@ Flag "naked" `lock()` and `unlock()`.
|
||||
|
||||
##### Reason
|
||||
|
||||
An unnamed local objects is a temporary that immediately goes out of scope.
|
||||
An unnamed local object is a temporary that immediately goes out of scope.
|
||||
|
||||
##### Example
|
||||
|
||||
unique_lock<mutex>(m1);
|
||||
lock_guard<mutex> {m2};
|
||||
lock(m1, m2);
|
||||
// global mutexes
|
||||
mutex m1;
|
||||
mutex m2;
|
||||
|
||||
This looks innocent enough, but it isn't.
|
||||
void f()
|
||||
{
|
||||
unique_lock<mutex>(m1); // (A)
|
||||
lock_guard<mutex> {m2}; // (B)
|
||||
// do work in critical section ...
|
||||
}
|
||||
|
||||
This looks innocent enough, but it isn't. At (A), `m1` is a default-constructed
|
||||
local `unique_lock`, which shadows the global `::m1` (and does not lock it).
|
||||
At (B) an unnamed temporary `lock_guard` is constructed and locks `::m2`,
|
||||
but immediately goes out of scope and unlocks `::m2` again.
|
||||
For the rest of the function `f()` neither mutex is locked.
|
||||
|
||||
##### Enforcement
|
||||
|
||||
@@ -16344,7 +16426,7 @@ Better:
|
||||
|
||||
##### Reason
|
||||
|
||||
`finally` from the [GSL](#S-gsl) is less verbose and harder to get wrong than `try`/`catch`.
|
||||
`finally` from the [GSL](#gsl-guidelines-support-library) is less verbose and harder to get wrong than `try`/`catch`.
|
||||
|
||||
##### Example
|
||||
|
||||
@@ -17337,7 +17419,7 @@ Flag template type arguments without concepts
|
||||
|
||||
##### Reason
|
||||
|
||||
"Standard" concepts (as provided by the [GSL](#S-gsl) and the ISO standard itself)
|
||||
"Standard" concepts (as provided by the [GSL](#gsl-guidelines-support-library) and the ISO standard itself)
|
||||
save us the work of thinking up our own concepts, are better thought out than we can manage to do in a hurry, and improve interoperability.
|
||||
|
||||
##### Note
|
||||
@@ -19331,10 +19413,6 @@ and M functions each containing a `using namespace X`with N lines of code in tot
|
||||
|
||||
[Don't write `using namespace` at global scope in a header file](#Rs-using-directive).
|
||||
|
||||
##### Enforcement
|
||||
|
||||
Flag multiple `using namespace` directives for different namespaces in a single source file.
|
||||
|
||||
### <a name="Rs-using-directive"></a>SF.7: Don't write `using namespace` at global scope in a header file
|
||||
|
||||
##### Reason
|
||||
@@ -19682,7 +19760,21 @@ Additions to `std` might clash with future versions of the standard.
|
||||
|
||||
##### Example
|
||||
|
||||
???
|
||||
namespace std { // BAD: violates standard
|
||||
|
||||
class My_vector {
|
||||
// . . .
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
namespace Foo { // GOOD: user namespace is allowed
|
||||
|
||||
class My_vector {
|
||||
// . . .
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
##### Enforcement
|
||||
|
||||
@@ -20322,7 +20414,7 @@ For writing to a file, there is rarely a need to `flush`.
|
||||
For string streams (specifically `ostringstream`), the insertion of an `endl` is entirely equivalent
|
||||
to the insertion of a `'\n'` character, but also in this case, `endl` might be significantly slower.
|
||||
|
||||
`endl` does *not* take care of producing a platform specific end-of-line sequence (like "\r\n" on
|
||||
`endl` does *not* take care of producing a platform specific end-of-line sequence (like `"\r\n"` on
|
||||
Windows). So for a string stream, `s << endl` just inserts a *single* character, `'\n'`.
|
||||
|
||||
##### Note
|
||||
@@ -20347,7 +20439,7 @@ It provides clocks for registering `time_points`.
|
||||
|
||||
C Standard Library rule summary:
|
||||
|
||||
* [S.C.1: Don't use setjmp/longjmp](#Rclib-jmp)
|
||||
* [SL.C.1: Don't use setjmp/longjmp](#Rclib-jmp)
|
||||
* [???](#???)
|
||||
* [???](#???)
|
||||
|
||||
@@ -20729,7 +20821,7 @@ and errors (when we didn't deal correctly with semi-constructed objects consiste
|
||||
};
|
||||
|
||||
Picture picture1(100, 100);
|
||||
// picture is ready-to-use here...
|
||||
// picture1 is ready-to-use here...
|
||||
|
||||
// not a valid size for y,
|
||||
// default contract violation behavior will call std::terminate then
|
||||
@@ -21046,7 +21138,7 @@ Prefer [construction](#Res-construct) or [named casts](#Res-casts-named) or `T{e
|
||||
[always initialize](#Res-always),
|
||||
possibly using [default constructors](#Rc-default0) or
|
||||
[default member initializers](#Rc-in-class-initializer).
|
||||
* <a name="Pro-type-unon"></a>Type.7: Avoid naked union:
|
||||
* <a name="Pro-type-union"></a>Type.7: Avoid naked union:
|
||||
[Use `variant` instead](#Ru-naked).
|
||||
* <a name="Pro-type-varargs"></a>Type.8: Avoid varargs:
|
||||
[Don't use `va_arg` arguments](#F-varargs).
|
||||
@@ -21131,6 +21223,8 @@ Eventually, use [the one voted into C++17](http://www.open-std.org/jtc1/sc22/wg2
|
||||
Some of the GSL types listed below might not be supported in the library you use due to technical reasons such as limitations in the current versions of C++.
|
||||
Therefore, please consult your GSL documentation to find out more.
|
||||
|
||||
For each GSL type below we state an invariant for that type. That invariant holds as long as user code only changes the state of a GSL object using the type's provided member/free functions (i.e., user code does not bypass the type's interface to change the object's value/bits by violating any other Guidelines rule).
|
||||
|
||||
Summary of GSL components:
|
||||
|
||||
* [GSL.view: Views](#SS-views)
|
||||
@@ -21180,7 +21274,9 @@ If something is not supposed to be `nullptr`, say so:
|
||||
* `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
|
||||
|
||||
A `span<T>` refers to zero or more mutable `T`s unless `T` is a `const` type.
|
||||
A `span<T>` refers to zero or more mutable `T`s unless `T` is a `const` type. All accesses to elements of the span, notably via `operator[]`, are guaranteed to be bounds-checked by default.
|
||||
|
||||
> Note: GSL's `span` (initially called `array_view`) was proposed for inclusion in the C++ standard library, and was adopted (with changes to its name and interface) except only that `std::span` does not provide for guaranteed bounds checking. Therefore GSL changed `span`'s name and interface to track `std::span` and should be exactly the same as `std::span`, and the only difference should be that GSL `span` is fully bounds-safe by default. If bounds-safety might affect its interface, then those change proposals should be brought back via the ISO C++ committee to keep `gsl::span` interface-compatible with `std::span`. If a future evolution of `std::span` adds bounds checking, `gsl::span` can be removed.
|
||||
|
||||
"Pointer arithmetic" is best done within `span`s.
|
||||
A `char*` that points to more than one `char` but is not a C-style string (e.g., a pointer into an input buffer) should be represented by a `span`.
|
||||
|
||||
Reference in New Issue
Block a user