update from master

This commit is contained in:
Sergey Zubkov
2023-10-12 17:05:22 -04:00
parent 55cfd7f94c
commit 7dce4d9c7a

View File

@@ -1,6 +1,6 @@
# <a name="main"></a>C++ Core Guidelines # <a name="main"></a>C++ Core Guidelines
September 29, 2023 October 12, 2023
Editors: Editors:
@@ -2310,8 +2310,8 @@ So, we write a class
public: public:
enum Opt { from_line = 1 }; enum Opt { from_line = 1 };
Istream() { } Istream() { }
Istream(zstring p) : owned{true}, inp{new ifstream{p}} {} // read from file Istream(czstring p) : owned{true}, inp{new ifstream{p}} {} // read from file
Istream(zstring p, Opt) : owned{true}, inp{new istringstream{p}} {} // read from command line Istream(czstring p, Opt) : owned{true}, inp{new istringstream{p}} {} // read from command line
~Istream() { if (owned) delete inp; } ~Istream() { if (owned) delete inp; }
operator istream&() { return *inp; } operator istream&() { return *inp; }
private: private:
@@ -3188,7 +3188,7 @@ A `struct` of many (individually cheap-to-move) elements might be in aggregate e
##### Exceptions ##### Exceptions
* For non-concrete types, such as types in an inheritance hierarchy, return the object by `unique_ptr` or `shared_ptr`. * For non-concrete types, such as types in an inheritance hierarchy, return the object by `unique_ptr` or `shared_ptr`.
* If a type is expensive to move (e.g., `array<BigPOD>`), consider allocating it on the free store and return a handle (e.g., `unique_ptr`), or passing it in a reference to non-`const` target object to fill (to be used as an out-parameter). * If a type is expensive to move (e.g., `array<BigTrivial>`), consider allocating it on the free store and return a handle (e.g., `unique_ptr`), or passing it in a reference to non-`const` target object to fill (to be used as an out-parameter).
* To reuse an object that carries capacity (e.g., `std::string`, `std::vector`) across multiple calls to the function in an inner loop: [treat it as an in/out parameter and pass by reference](#Rf-out-multi). * To reuse an object that carries capacity (e.g., `std::string`, `std::vector`) across multiple calls to the function in an inner loop: [treat it as an in/out parameter and pass by reference](#Rf-out-multi).
##### Example ##### Example
@@ -3301,7 +3301,7 @@ To compare, if we passed out all values as return values, we would something lik
return {in, move(s)}; return {in, move(s)};
} }
for (auto p = get_string(cin); p.first; ) { for (auto p = get_string(cin); p.first; p.second = get_string(p.first).second) {
// do something with p.second // do something with p.second
} }
@@ -6438,9 +6438,11 @@ A non-throwing move will be used more efficiently by standard-library and langua
public: public:
Vector(Vector&& a) noexcept :elem{a.elem}, sz{a.sz} { a.elem = nullptr; a.sz = 0; } Vector(Vector&& a) noexcept :elem{a.elem}, sz{a.sz} { a.elem = nullptr; a.sz = 0; }
Vector& operator=(Vector&& a) noexcept { Vector& operator=(Vector&& a) noexcept {
if (&a != this) {
delete elem; delete elem;
elem = a.elem; a.elem = nullptr; elem = a.elem; a.elem = nullptr;
sz = a.sz; a.sz = 0; sz = a.sz; a.sz = 0;
}
return *this; return *this;
} }
// ... // ...
@@ -10858,7 +10860,7 @@ The *always initialize* rule is a style rule aimed to improve maintainability as
Here is an example that is often considered to demonstrate the need for a more relaxed rule for initialization Here is an example that is often considered to demonstrate the need for a more relaxed rule for initialization
widget i; // "widget" a type that's expensive to initialize, possibly a large POD widget i; // "widget" a type that's expensive to initialize, possibly a large trivial type
widget j; widget j;
if (cond) { // bad: i and j are initialized "late" if (cond) { // bad: i and j are initialized "late"
@@ -11093,7 +11095,8 @@ For containers, there is a tradition for using `{...}` for a list of elements an
int x {7.9}; // error: narrowing int x {7.9}; // error: narrowing
int y = 7.9; // OK: y becomes 7. Hope for a compiler warning int y = 7.9; // OK: y becomes 7. Hope for a compiler warning
int z = gsl::narrow_cast<int>(7.9); // OK: you asked for it int z {gsl::narrow_cast<int>(7.9)}; // OK: you asked for it
auto zz = gsl::narrow_cast<int>(7.9); // OK: you asked for it
##### Note ##### Note
@@ -11365,8 +11368,6 @@ Also, `#` and `##` encourages the definition and use of macros:
There are workarounds for low-level string manipulation using macros. For example: There are workarounds for low-level string manipulation using macros. For example:
string s = "asdf" "lkjh"; // ordinary string literal concatenation
enum E { a, b }; enum E { a, b };
template<int x> template<int x>
@@ -11378,9 +11379,10 @@ There are workarounds for low-level string manipulation using macros. For exampl
} }
} }
void f(int x, int y) void f()
{ {
string sx = stringify<x>(); string s1 = stringify<a>();
string s2 = stringify<b>();
// ... // ...
} }
@@ -16856,19 +16858,28 @@ Prevents accidental or hard-to-notice change of value.
for (int i : c) cout << i << '\n'; // BAD: just reading for (int i : c) cout << i << '\n'; // BAD: just reading
##### Exception ##### Exceptions
A local variable that is returned by value and is cheaper to move than copy should not be declared `const`
because it can force an unnecessary copy.
std::vector<int> f(int i)
{
std::vector<int> v{ i, i, i }; // const not needed
return v;
}
Function parameters passed by value are rarely mutated, but also rarely declared `const`. Function parameters passed by value are rarely mutated, but also rarely declared `const`.
To avoid confusion and lots of false positives, don't enforce this rule for function parameters. To avoid confusion and lots of false positives, don't enforce this rule for function parameters.
void f(const char* const p); // pedantic
void g(const int i) { ... } // pedantic void g(const int i) { ... } // pedantic
Note that a function parameter is a local variable so changes to it are local. Note that a function parameter is a local variable so changes to it are local.
##### Enforcement ##### Enforcement
* Flag non-`const` variables that are not modified (except for parameters to avoid many false positives) * Flag non-`const` variables that are not modified (except for parameters to avoid many false positives
and returned local variables)
### <a name="Rconst-fct"></a>Con.2: By default, make member functions `const` ### <a name="Rconst-fct"></a>Con.2: By default, make member functions `const`
@@ -18437,21 +18448,22 @@ Specialization offers a powerful mechanism for providing alternative implementat
This is a simplified version of `std::copy` (ignoring the possibility of non-contiguous sequences) This is a simplified version of `std::copy` (ignoring the possibility of non-contiguous sequences)
struct pod_tag {}; struct trivially_copyable_tag {};
struct non_pod_tag {}; struct non_trivially_copyable_tag {};
template<class T> struct copy_trait { using tag = non_pod_tag; }; // T is not "plain old data" // T is not trivially copyable
template<class T> struct copy_trait { using tag = non_trivially_copyable_tag; };
template<> struct copy_trait<int> { using tag = pod_tag; }; // int is "plain old data" // int is trivially copyable
template<> struct copy_trait<int> { using tag = trivially_copyable_tag; };
template<class Iter> template<class Iter>
Out copy_helper(Iter first, Iter last, Iter out, pod_tag) Out copy_helper(Iter first, Iter last, Iter out, trivially_copyable_tag)
{ {
// use memmove // use memmove
} }
template<class Iter> template<class Iter>
Out copy_helper(Iter first, Iter last, Iter out, non_pod_tag) Out copy_helper(Iter first, Iter last, Iter out, non_trivially_copyable_tag)
{ {
// use loop calling copy constructors // use loop calling copy constructors
} }
@@ -18459,7 +18471,8 @@ This is a simplified version of `std::copy` (ignoring the possibility of non-con
template<class Iter> template<class Iter>
Out copy(Iter first, Iter last, Iter out) Out copy(Iter first, Iter last, Iter out)
{ {
return copy_helper(first, last, out, typename copy_trait<Value_type<Iter>>::tag{}) using tag_type = typename copy_trait<std::iter_value_t<Iter>>;
return copy_helper(first, last, out, tag_type{})
} }
void use(vector<int>& vi, vector<int>& vi2, vector<string>& vs, vector<string>& vs2) void use(vector<int>& vi, vector<int>& vi2, vector<string>& vs, vector<string>& vs2)
@@ -18472,10 +18485,10 @@ This is a general and powerful technique for compile-time algorithm selection.
##### Note ##### Note
When `concept`s become widely available such alternatives can be distinguished directly: With C++20 constraints, such alternatives can be distinguished directly:
template<class Iter> template<class Iter>
requires Pod<Value_type<Iter>> requires std::is_trivially_copyable_v<std::iter_value_t<Iter>>
Out copy_helper(In, first, In last, Out out) Out copy_helper(In, first, In last, Out out)
{ {
// use memmove // use memmove
@@ -19208,6 +19221,7 @@ Source file rule summary:
* [SF.10: Avoid dependencies on implicitly `#include`d names](#Rs-implicit) * [SF.10: Avoid dependencies on implicitly `#include`d names](#Rs-implicit)
* [SF.11: Header files should be self-contained](#Rs-contained) * [SF.11: Header files should be self-contained](#Rs-contained)
* [SF.12: Prefer the quoted form of `#include` for files relative to the including file and the angle bracket form everywhere else](#Rs-incform) * [SF.12: Prefer the quoted form of `#include` for files relative to the including file and the angle bracket form everywhere else](#Rs-incform)
* [SF.13: Use portable header identifiers in `#include` statements](#Rs-portable-header-id)
* [SF.20: Use `namespace`s to express logical structure](#Rs-namespace) * [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) * [SF.21: Don't use an unnamed (anonymous) namespace in a header](#Rs-unnamed)
@@ -19618,7 +19632,7 @@ Nevertheless, the guidance is to use the quoted form for including files that ex
#include <string> // From the standard library, requires the <> form #include <string> // From the standard library, requires the <> form
#include <some_library/common.h> // A file that is not locally relative, included from another library; use the <> form #include <some_library/common.h> // A file that is not locally relative, included from another library; use the <> form
#include "foo.h" // A file locally relative to foo.cpp in the same project, use the "" form #include "foo.h" // A file locally relative to foo.cpp in the same project, use the "" form
#include "foo_utils/utils.h" // A file locally relative to foo.cpp in the same project, use the "" form #include "util/util.h" // A file locally relative to foo.cpp in the same project, use the "" form
#include <component_b/bar.h> // A file in the same project located via a search path, use the <> form #include <component_b/bar.h> // A file in the same project located via a search path, use the <> form
##### Note ##### Note
@@ -19631,6 +19645,34 @@ Library creators should put their headers in a folder and have clients include t
A test should identify whether headers referenced via `""` could be referenced with `<>`. A test should identify whether headers referenced via `""` could be referenced with `<>`.
### <a name="Rs-portable-header-id"></a>SF.13: Use portable header identifiers in `#include` statements
##### Reason
The [standard](http://eel.is/c++draft/cpp.include) does not specify how compilers uniquely locate headers from an identifier in an `#include` directive, nor does it specify what constitutes uniqueness. For example, whether the implementation considers the identifiers to be case-sensitive, or whether the identifiers are file system paths to a header file, and if so, how a hierarchical file system path is delimited.
To maximize the portability of `#include` directives across compilers, guidance is to:
* use case-sensitivity for the header identifier, matching how the header is defined by the standard, specification, implementation, or file that provides the header.
* when the header identifier is a hierarchical file path, use forward-slash `/` to delimit path components as this is the most widely-accepted path-delimiting character.
##### Example
// good examples
#include <vector>
#include <string>
#include "util/util.h"
// bad examples
#include <VECTOR> // bad: the standard library defines a header identified as <vector>, not <VECTOR>
#include <String> // bad: the standard library defines a header identified as <string>, not <String>
#include "Util/Util.H" // bad: the header file exists on the file system as "util/util.h"
#include "util\util.h" // bad: may not work if the implementation interprets `\u` as an escape sequence, or where '\' is not a valid path separator
##### Enforcement
It is only possible to enforce on implementations where header identifiers are case-sensitive and which only support `/` as a file path delimiter.
### <a name="Rs-namespace"></a>SF.20: Use `namespace`s to express logical structure ### <a name="Rs-namespace"></a>SF.20: Use `namespace`s to express logical structure
##### Reason ##### Reason
@@ -21594,7 +21636,8 @@ ISO Standard, use lower case only and digits, separate words with underscores:
* `vector` * `vector`
* `my_map` * `my_map`
Avoid double underscores `__`. Avoid identifier names that contain double underscores `__` or that start with an underscore followed by a capital letter (e.g., `_Throws`).
Such identifiers are reserved for the C++ implementation.
##### Example ##### Example
@@ -21643,7 +21686,7 @@ To avoid confusing macros with names that obey scope and type rules.
##### Note ##### Note
This rule applies to non-macro symbolic constants: In particular, this avoids confusing macros with non-macro symbolic constants (see also [Enum.5: Don't use `ALL_CAPS` for enumerators](#Renum-caps))
enum bad { BAD, WORSE, HORRIBLE }; // BAD enum bad { BAD, WORSE, HORRIBLE }; // BAD