I.30: Encapsulate rule violations

Fiexed #893 by moving the bad example from ES.28 to a new rule: I.30:
Encapsulate rule violations.

I may inadvertenly have invented a new suppression syntax
This commit is contained in:
Bjarne Stroustrup 2017-05-16 14:59:55 -04:00
parent 17ccab5836
commit 9620ea8d43

View File

@ -1201,6 +1201,7 @@ Interface rule summary:
* [I.24: Avoid adjacent unrelated parameters of the same type](#Ri-unrelated)
* [I.25: Prefer abstract classes as interfaces to class hierarchies](#Ri-abstract)
* [I.26: If you want a cross-compiler ABI, use a C-style subset](#Ri-abi)
* [I.30: Encapsulate rule violations](#Ri-encapsulate)
See also
@ -2131,6 +2132,69 @@ If you use a single compiler, you can use full C++ in interfaces. That may requi
(Not enforceable) It is difficult to reliably identify where an interface forms part of an ABI.
### <a name="Ri-encapsulate"></a>I.30: Encapsulate rule violations
##### Reason
To keep code simple and safe.
Sometimes, ugly, unsafe, or error-prone techniques are necessary for logical or performance reasons.
If so, keep them local, rather than "infecting" interfaces so that larger groups of programmers have to be aware of the
subtleties.
Implementation complexity should, if at all possible, not leak through interfaces into user code.
##### Example
Consider a program that, depending on some form of input (e.g., arguments to `main`), should consume input
from a file, from the command line, or from standard input.
We might write
bool owned;
owner<istream*> inp;
switch (source) {
case std_in: owned = false; inp = &cin;
case command_line: owned = true; inp = new istringstream{argv[2]};
case file: owned = true; inp = new ifstream{argv[2]};
}
istream& in = *inp;
This violated the ruly [against uninitialized variables](#Res-always),
the rule against [ignoring ownership](#Ri-raw),
and the rule [against magic constants](#Res-magic) .
In particular, someone has to remember to somewhere write
if (owned) delete inp;
We could handle this particular example by using `unique_ptr` with a special deleter that does nothing for `cin`,
but that's complicated for novices (who can easily encounter this problem) and the example is an example of a more general
problem where a property that we would like to consider static (here, ownership) needs infrequesntly be addressed
at run time.
The common, most frequent, and safest examples can be handled statically, so we don't want to add cost and complexity to those.
But we must also cope with the uncommon, less-safe, and necessarily more expensive cases.
Such examples are discussed in [[Str15]](http://www.stroustrup.com/resource-model.pdf).
So, we write a class
class Istream { [[gsl::suppress(lifetime)]]
public:
enum Opt { from_line=1 };
Istream() { }
Istream(zstring p) :owned{true}, inp{new ifstream{p}} {} // read from file
Istream(zstring p,Opt) :owned{true}, inp{new istringstream{p}} {} // read from command line
~Itream() { if (owned) delete inp; }
operator istream& () { return *inp; }
private:
bool owned = false;
istream* inp = &cin;
};
Now, the dynamic nature of `istream` ownership has been encapsulated.
Presumably, a bit of checking for potential errors would be added in real code.
##### Enforcement
* Hard, it is hard to decide what rule-breaking code is essential
* flag rule suppression that enable rule-violations to cross interfaces
# <a name="S-functions"></a>F: Functions
A function specifies an action or a computation that takes the system from one consistent state to the next. It is the fundamental building block of programs.
@ -10245,17 +10309,6 @@ It nicely encapsulates local initialization, including cleaning up scratch varia
If at all possible, reduce the conditions to a simple set of alternatives (e.g., an `enum`) and don't mix up selection and initialization.
##### Example
bool owned = false;
owner<istream*> inp = [&]{
switch (source) {
case default: owned = false; return &cin;
case command_line: owned = true; return new istringstream{argv[2]};
case file: owned = true; return new ifstream{argv[2]};
}();
istream& in = *inp;
##### Enforcement
Hard. At best a heuristic. Look for an uninitialized variable followed by a loop assigning to it.