mirror of
https://github.com/isocpp/CppCoreGuidelines.git
synced 2024-03-22 13:30:58 +08:00
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:
parent
17ccab5836
commit
9620ea8d43
|
@ -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.
|
||||
|
|
Loading…
Reference in New Issue
Block a user