@ -4183,7 +4187,7 @@ Flag classes declared with `struct` if there is a `private` or `protected` membe
Encapsulation.
Information hiding.
Minimize the chance of untended access.
Minimize the chance of unintended access.
This simplifies maintenance.
##### Example
@ -5084,7 +5088,7 @@ If you really have to, look at [factory functions](#Rc-factory).
One reason people have used `init()` functions rather than doing the initialization work in a constructor has been to avoid code replication.
[Delegating constructors](#Rc-delegating) and [default member initialization](#Rc-in-class-initializer) do that better.
Another reason is been to delay initialization until an object is needed; the solution to that is often [not to declare a variable until it can be properly initialized](#Res-init)
Another reason has been to delay initialization until an object is needed; the solution to that is often [not to declare a variable until it can be properly initialized](#Res-init)
##### Enforcement
@ -6855,6 +6859,25 @@ Since each implementation derived from its interface as well as its implementati
As mentioned, this is just one way to construct a dual hierarchy.
The implementation hierarchy can be used directly, rather than through the abstract interface.
@ -9864,7 +9915,6 @@ Flag variable and constant declarations with multiple declarators (e.g., `int* p
Consider:
auto p = v.begin(); // vector<int>::iterator
auto s = v.size();
auto h = t.future();
auto q = make_unique<int[]>(s);
auto f = [](int x){ return x + 10; };
@ -10052,11 +10102,6 @@ This cannot trivially be rewritten to initialize `i` and `j` with initializers.
Note that for types with a default constructor, attempting to postpone initialization simply leads to a default initialization followed by an assignment.
A popular reason for such examples is "efficiency", but a compiler that can detect whether we made a used-before-set error can also eliminate any redundant double initialization.
At the cost of repeating `cond` we could write:
widget i = (cond) ? f1() : f3();
widget j = (cond) ? f2() : f4();
Assuming that there is a logical connection between `i` and `j`, that connection should probably be expressed in code:
pair<widget,widget> make_related_widgets(bool x)
@ -10064,25 +10109,13 @@ Assuming that there is a logical connection between `i` and `j`, that connection
return (x) ? {f1(), f2()} : {f3(), f4() };
}
auto init = make_related_widgets(cond);
widget i = init.first;
widget j = init.second;
Obviously, what we really would like is a construct that initialized n variables from a `tuple`. For example:
auto [i, j] = make_related_widgets(cond); // C++17, not C++14
auto [i, j] = make_related_widgets(cond); // C++17
Today, we might approximate that using `tie()`:
widget i; // bad: uninitialized variable
widget j;
tie(i, j) = make_related_widgets(cond);
This may be seen as an example of the *immediately initialize from input* exception below.
##### Note
Creating optimal and equivalent code from all of these examples should be well within the capabilities of modern C++ compilers
(but don't make performance claims without measuring; a compiler may very well not generate optimal code for every example and
there may be language rules preventing some optimization that you would have liked in a particular case).
Complex initialization has been popular with clever programmers for decades.
It has also been a major source of errors and complexity.
Many such errors are introduced during maintenance years after the initial implementation.
##### Example
@ -10107,12 +10140,6 @@ The compiler will flag the uninitialized `cm3` because it is a `const`, but it w
Usually, a rare spurious member initialization is worth the absence of errors from lack of initialization and often an optimizer
can eliminate a redundant initialization (e.g., an initialization that occurs immediately before an assignment).
##### Note
Complex initialization has been popular with clever programmers for decades.
It has also been a major source of errors and complexity.
Many such errors are introduced during maintenance years after the initial implementation.
##### Exception
If you are declaring an object that is just about to be initialized from input, initializing it would cause a double initialization.
for (int i = 1; i <v.size();++i)//touchestwoelements:can'tbearange-for
for (gsl::index i = 1; i <v.size();++i)//touchestwoelements:can'tbearange-for
cout <<v[i]+v[i-1]<<'\n';
for (int i = 0; i <v.size();++i)//possiblesideeffect:can'tbearange-for
for (gsl::index i = 0; i <v.size();++i)//possiblesideeffect:can'tbearange-for
cout <<f(v,&v[i])<<'\n';
for (int i = 0; i <v.size();++i){//bodymesseswithloopvariable:can'tbearange-for
for (gsl::index i = 0; i <v.size();++i){//bodymesseswithloopvariable:can'tbearange-for
if (i % 2 == 0)
continue; // skip even elements
else
@ -12006,7 +12033,7 @@ Readability: the complete logic of the loop is visible "up front". The scope of
##### Example
for (int i = 0; i <vec.size();i++){
for (gsl::index i = 0; i <vec.size();i++){
// do work
}
@ -12437,6 +12464,23 @@ For example:
This invokes `istream`'s `operator bool()`.
##### Note
Explicit comparison of an integer to `0` is in general not redundant.
The reason is that (as opposed to pointers and Booleans) an integer often has more than two reasonable values.
Furthermore `0` (zero) is often used to indicate success.
Consequently, it is best to be specific about the comparison.
void f(int i)
{
if (i) // suspect
// ...
if (i == success) // possibly better
// ...
}
Always remember that an integer can have more that two values.
##### Example, bad
It has been noted that
@ -12449,7 +12493,7 @@ Being verbose and writing
if(strcmp(p1, p2) != 0) { ... } // are the two C-style strings equal? (mistake!)
would not save you.
would not in itself save you.
##### Note
@ -12488,11 +12532,13 @@ It is harder to spot the problem in more realistic examples.
##### Note
Unfortunately, C++ uses signed integers for array subscripts and the standard library uses unsigned integers for container subscripts.
This precludes consistency.
This precludes consistency. Use `gsl::index` for subscripts; [see ES.107](#Res-subscripts).
##### Enforcement
Compilers already know and sometimes warn.
* Compilers already know and sometimes warn.
* (To avoid noise) Do not flag on a mixed signed/unsigned comparison where one of the arguments is `sizeof` or a call to container `.size()` and the other is `ptrdiff_t`.
### <aname="Res-unsigned"></a>ES.101: Use unsigned types for bit manipulation
@ -12559,25 +12605,29 @@ is going to be surprising for many programmers.
##### Example
The standard library uses unsigned types for subscripts.
The build-in array uses signed types for subscripts.
The built-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;
vector<int> v(10);
// compares signed to unsigned; some compilers warn
for (int i = 0; v.size() <10;++i)v[i]=i;
// compares signed to unsigned; some compilers warn, but we should not
for (gsl::index i = 0; v.size() <10;++i)v[i]=i;
int a2[-2]; // error: negative size
// OK, but the number of ints (4294967294) is so large that we should get an exception
vector<int> v2(-2);
Use `gsl::index` for subscripts; [see ES.107](#Res-subscripts).
##### Enforcement
* Flag mixed signed and unsigned arithmetic
* Flag results of unsigned arithmetic assigned to or printed as signed.
* Flag unsigned literals (e.g. `-2`) used as container subscripts.
* (To avoid noise) Do not flag on a mixed signed/unsigned comparison where one of the arguments is `sizeof` or a call to container `.size()` and the other is `ptrdiff_t`.
Hard: there is a lot of code using `unsigned` and we don't offer a practical positive number type.
### <aname="Res-subscripts"></a>ES.107: Don't use `unsigned` for subscripts
### <aname="Res-subscripts"></a>ES.107: Don't use `unsigned` for subscripts, prefer `gsl::index`
##### Reason
To avoid signed/unsigned confusion.
To enable better optimization.
To enable better error detection.
To avoid the pitfalls with `auto` and `int`.
##### Example, bad
vector<int> vec {1, 2, 3, 4, 5};
vector<int> vec = /*...*/;
for (int i = 0; i <vec.size();i+=2)//mix intandunsigned
for (int i = 0; i <vec.size();i+=2)//may notbebigenough
cout <<vec[i]<<'\n';
for (unsigned i = 0; i <vec.size();i+=2)//riskwraparound
cout <<vec[i]<<'\n';
for (auto i = 0; i <vec.size();i+=2)//maynotbebigenough
cout <<vec[i]<<'\n';
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)//mixintandunsigned
for (auto i = vec.size()-1; i >= 0; i -= 2) // bug
cout <<vec[i]<<'\n';
for (int i = vec.size()-1; i >= 0; i -= 2) // may not be big enough
cout <<vec[i]<<'\n';
##### Example, good
vector<int> vec = /*...*/;
for (gsl::index i = 0; i <vec.size();i+=2)//ok
cout <<vec[i]<<'\n';
for (gsl::index i = vec.size()-1; i >= 0; i -= 2) // ok
cout <<vec[i]<<'\n';
##### Note
The built-in array uses signed subscripts.
The standard-library containers use unsigned subscripts.
Thus, no perfect and fully compatible solution is possible.
Given the known problems with unsigned and signed/unsigned mixtures, better stick to (signed) integers.
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`.
##### Example
@ -12775,7 +12839,7 @@ Given the known problems with unsigned and signed/unsigned mixtures, better stic
struct My_container {
public:
// ...
T& operator[](int i); // not unsigned
T& operator[](gsl::index i); // not unsigned
// ...
};
@ -12793,7 +12857,11 @@ Alternatives for users
##### Enforcement
Very tricky as long as the standard-library containers get it wrong.
* Very tricky as long as the standard-library containers get it wrong.
* (To avoid noise) Do not flag on a mixed signed/unsigned comparison where one of the arguments is `sizeof` or a call to container `.size()` and the other is `ptrdiff_t`.
# <aname="S-performance"></a>Per: Performance
@ -13101,7 +13169,82 @@ Type violations, weak types (e.g. `void*`s), and low-level code (e.g., manipulat
### <aname="Rper-Comp"></a>Per.11: Move computation from run time to compile time
???
##### Reason
To decrease code size and run time.
To avoid data races by using constants.
To catch errors at compile time (and thus eliminate the need for error-handling code).
Code like the initialization of `s2` isn't uncommon, especially for initialization that's a bit more complicated than `square()`.
However, compared to the initialization of `s3` there are two problems:
* we suffer the overhead of a function call at run time
* `s2` just might be accessed by another thread before the initialization happens.
Note: you can't have a data race on a constant.
##### Example
Consider a popular technique for providing a handle for storing small objects in the handle itself and larger ones on the heap.
constexpr int on_stack_max = 20;
template<typenameT>
struct Scoped { // store a T in Scoped
// ...
T obj;
};
template<typenameT>
struct On_heap { // store a T on the free store
// ...
T* objp;
};
template<typenameT>
using Handle = typename std::conditional<(sizeof(T) <= on_stack_max),
Scoped<T>, // first alternative
On_heap<T> // second alternative
>::type;
void f()
{
Handle<double> v1; // the double goes on the stack
Handle<std::array<double,200>> v2; // the array goes on the free store
// ...
}
Assume that `Scoped` and `On_heap` provide compatible user interfaces.
Here we compute the optimal type to use at compile time.
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,
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.
##### Enforcement
* Look for simple functions that might be constexpr (but are not).
* Look for functions called with all constant-expression arguments.
@ -13391,6 +13534,7 @@ Making `surface_readings` be `const` (with respect to this function) allow reaso
Immutable data can be safely and efficiently shared.
No locking is needed: You can't have a data race on a constant.
See also [CP.mess: Message Passing](#SScp-mess) and [CP.31: prefer pass by value](#C#Rconc-data-by-value).
##### Enforcement
@ -14694,7 +14838,7 @@ C++ implementations tend to be optimized based on the assumption that exceptions
int find_index(vector<string>& vec, const string& x)
{
try {
for (int i = 0; i <vec.size();++i)
for (gsl::index i = 0; i <vec.size();++i)
if (vec[i] == x) throw i; // found x
} catch (int i) {
return i;
@ -15777,9 +15921,9 @@ Templates can also be used for meta-programming; that is, programs that compose
A central notion in generic programming is "concepts"; that is, requirements on template arguments presented as compile-time predicates.
"Concepts" are defined in an ISO Technical specification: [concepts](http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2015/n4553.pdf).
A draft of a set of standard-library concepts can be found in another ISO TS: [ranges](http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2016/n4569.pdf)
Currently (July 2016), concepts are supported only in GCC 6.1.
Concepts are supported in GCC 6.1 and later.
Consequently, we comment out uses of concepts in examples; that is, we use them as formalized comments only.
If you use GCC 6.1, you can uncomment them.
If you use GCC 6.1 or later, you can uncomment them.
Template use rule summary:
@ -15934,9 +16078,9 @@ is to efficiently generalize operations/algorithms over a set of types with simi
The `requires` in the comments are uses of `concepts`.
"Concepts" are defined in an ISO Technical specification: [concepts](http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2015/n4553.pdf).
Currently (July 2016), concepts are supported only in GCC 6.1.
Concepts are supported in GCC 6.1 and later.
Consequently, we comment out uses of concepts in examples; that is, we use them as formalized comments only.
If you use GCC 6.1, you can uncomment them.
If you use GCC 6.1 or later, you can uncomment them.
##### Enforcement
@ -16127,9 +16271,9 @@ or equivalently and more succinctly:
"Concepts" are defined in an ISO Technical specification: [concepts](http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2015/n4553.pdf).
A draft of a set of standard-library concepts can be found in another ISO TS: [ranges](http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2016/n4569.pdf)
Currently (July 2016), concepts are supported only in GCC 6.1.
Concepts are supported in GCC 6.1 and later.
Consequently, we comment out uses of concepts in examples; that is, we use them as formalized comments only.
If you use GCC 6.1, you can uncomment them:
If you use GCC 6.1 or later, you can uncomment them:
template<typenameIter,typenameVal>
requires Input_iterator<Iter>
@ -16232,9 +16376,9 @@ The shorter versions better match the way we speak. Note that many templates don
"Concepts" are defined in an ISO Technical specification: [concepts](http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2015/n4553.pdf).
A draft of a set of standard-library concepts can be found in another ISO TS: [ranges](http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2016/n4569.pdf)
Currently (July 2016), concepts are supported only in GCC 6.1.
Concepts are supported in GCC 6.1 and later.
Consequently, we comment out uses of concepts in examples; that is, we use them as formalized comments only.
If you use a compiler that supports concepts (e.g., GCC 6.1), you can remove the `//`.
If you use a compiler that supports concepts (e.g., GCC 6.1 or later), you can remove the `//`.
##### Enforcement
@ -16248,7 +16392,7 @@ Concepts are meant to represent fundamental concepts in an application domain (h
Similarly throwing together a set of syntactic constraints to be used for a the arguments for a single class or algorithm is not what concepts were designed for
and will not give the full benefits of the mechanism.
Obviously, defining concepts will be most useful for code that can use an implementation (e.g., GCC 6.1),
Obviously, defining concepts will be most useful for code that can use an implementation (e.g., GCC 6.1 or later),
but defining concepts is in itself a useful design technique and help catch conceptual errors and clean up the concepts (sic!) of an implementation.
### <aname="Rt-low"></a>T.20: Avoid "concepts" without meaningful semantics
@ -18561,6 +18705,13 @@ For a variable-length array, use `std::vector`, which additionally can change it
Use `gsl::span` for non-owning references into a container.
##### Note
Comparing the performance of a fixed-sized array allocated on the stack against a `vector` with its elements on the free store is bogus.
You could just as well compare a `std::array` on the stack against the result of a `malloc()` accessed through a pointer.
For most code, even the difference between stack allocation and free-store allocation doesn't matter, but the convenience and safety of `vector` does.
People working with code for which that difference matters are quite capable of choosing between `array` and `vector`.
##### Enforcement
* Flag declaration of a C array inside a function or class that also declares an STL container (to avoid excessive noisy warnings on legacy non-STL code). To fix: At least change the C array to a `std::array`.
@ -19875,12 +20026,13 @@ for example, `Expects(p != nullptr)` will become `[[expects: p != nullptr]]`.
## <aname="SS-utilities"></a>GSL.util: Utilities
* `finally` // `finally(f)` makes a `final_action{f}` with a destructor that invokes `f`
* `narrow_cast` // `narrow_cast<T>(x)` is `static_cast<T>(x)`
* `narrow` // `narrow<T>(x)` is `static_cast<T>(x)` if `static_cast<T>(x) == x` or it throws `narrowing_error`
* `[[implicit]]` // "Marker" to put on single-argument constructors to explicitly make them non-explicit.
* `move_owner` // `p = move_owner(q)` means `p = q` but ???
* `finally`// `finally(f)` makes a `final_action{f}` with a destructor that invokes `f`
* `narrow_cast`// `narrow_cast<T>(x)` is `static_cast<T>(x)`
* `narrow`// `narrow<T>(x)` is `static_cast<T>(x)` if `static_cast<T>(x) == x` or it throws `narrowing_error`
* `[[implicit]]`// "Marker" to put on single-argument constructors to explicitly make them non-explicit.
* `move_owner`// `p = move_owner(q)` means `p = q` but ???
* `joining_thread` // a RAII style version of `std::thread` that joins.
* `index` // a type to use for all container and array indexing (currently an alias for `ptrdiff_t`)