diff --git a/CppCoreGuidelines.md b/CppCoreGuidelines.md index 54d6498..61ea726 100644 --- a/CppCoreGuidelines.md +++ b/CppCoreGuidelines.md @@ -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. +### 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 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 + # 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 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.