From 67191255fa320c6ee8a06811bc3b0153ecc302dc Mon Sep 17 00:00:00 2001 From: Bjarne Stroustrup Date: Thu, 25 Aug 2016 16:18:48 -0400 Subject: [PATCH] Updates to NR section --- CppCoreGuidelines.md | 346 +++++++++++++++++++++++++++++++++++++++---- 1 file changed, 316 insertions(+), 30 deletions(-) diff --git a/CppCoreGuidelines.md b/CppCoreGuidelines.md index 13b4ad9..e948366 100644 --- a/CppCoreGuidelines.md +++ b/CppCoreGuidelines.md @@ -1,6 +1,6 @@ # C++ Core Guidelines -August 24, 2016 +August 25, 2016 Editors: @@ -983,17 +983,27 @@ Messy, low-level code breeds more such code. int sz = 100; int* p = (int*) malloc(sizeof(int) * sz); + int count = 0; // ... - if (count == sz) - p = (int*) realloc(p, sizeof(int) * sz * 2); - // ... + for (;;) { + // ... read an int into x, exit loop if end of file is reached ... + // ... check that x is valid ... + if (count == sz) + p = (int*) realloc(p, sizeof(int) * sz * 2); + p[count++] = x; + // ... + } This is low-level, verbose, and error-prone. +Fo example, we "forgot" to test for mememory exhaustion. Instead, we could use `vector`: vector v(100); - - v.push_back(yet_another)int); + // ... + for (int x; cin>>x; ) { + // ... check that x is valid ... + v.push_back(x); + } ##### Note @@ -1170,6 +1180,23 @@ This is one of the most effective solutions to problems related to initializatio In a multi-threaded environment the initialization of the static object does not introduce a race condition (unless you carelessly access a shared object from within its constructor). +Note that the initialization of a local `static` does not imply a race condition. +However, if the destruction of `X` involves an operation that needs to be synchronized we must use a less imple solution. +For example: + + X& myX() + { + static auto p = new X {3}; + return *p; // potential leak + } + +Now someone has to `delete` that object in some suitably thread-safe way. +That's error-prone, so we don't use that technique unless + +* `myX` is in multithreaded code, +* that `X` object needs to be destroyed (e.g., because it releases a resource), and +* `X`'s destructor's code needs to be synchronized. + If you, as many do, define a singleton as a class for which only one object is created, functions like `myX` are not singletons, and this useful technique is not an exception to the no-singleton rule. ##### Enforcement @@ -9135,7 +9162,7 @@ For containers, there is a tradition for using `{...}` for a list of elements an Initialization of a variable declared using `auto` with a single value, e.g., `{v}`, had surprising results until recently: auto x1 {7}; // x1 is an int with the value 7 - auto x2 = {7}; // x2 is an initializer_list with an element 7 + auto x2 = {7}; // x2 is an initializer_list with an element 7 (this will will change to "element 7" in C++17) auto x11 {7, 8}; // error: two initializers auto x22 = {7, 8}; // x2 is an initializer_list with elements 7 and 8 @@ -9146,6 +9173,10 @@ Use `={...}` if you really want an `initializer_list` auto fib10 = {0, 1, 2, 3, 5, 8, 13, 25, 38, 63}; // fib10 is a list +##### Note + +Old habits die hard, so this rule is hard to apply consistently, especially as there are so many cases where `=` is innocent. + ##### Example template @@ -13183,17 +13214,17 @@ Generality. Re-use. Efficiency. Encourages consistent definition of user types. Conceptually, the following requirements are wrong because what we want of `T` is more than just the very low-level concepts of "can be incremented" or "can be added": - template + template // requires Incrementable - A sum1(vector& v, A s) + T sum1(vector& v, T s) { for (auto x : v) s += x; return s; } - template + template // requires Simple_number - A sum2(vector& v, A s) + T sum2(vector& v, T s) { for (auto x : v) s = s + x; return s; @@ -13204,9 +13235,9 @@ And, in this case, missed an opportunity for a generalization. ##### Example - template + template // requires Arithmetic - A sum(vector& v, A s) + T sum(vector& v, T s) { for (auto x : v) s += x; return s; @@ -15305,14 +15336,23 @@ Source file rule summary: ##### Reason -It's a longstanding convention. But consistency is more important, so if your project uses something else, follow that. +It's a longstanding convention. +But consistency is more important, so if your project uses something else, follow that. ##### Note -This convention reflects a common use pattern: Headers are more often shared with C to compile as both C++ and C, which typically uses `.h`, and it's easier to name all headers `.h` instead of having different extensions for just those headers that are intended to be shared with C. On the other hand, implementation files are rarely shared with C and so should typically be distinguished from `.c` files, so it's normally best to name all C++ implementation files something else (such as `.cpp`). +This convention reflects a common use pattern: +Headers are more often shared with C to compile as both C++ and C, which typically uses `.h`, +and it's easier to name all headers `.h` instead of having different extensions for just those headers that are intended to be shared with C. +On the other hand, implementation files are rarely shared with C and so should typically be distinguished from `.c` files, +so it's normally best to name all C++ implementation files something else (such as `.cpp`). The specific names `.h` and `.cpp` are not required (just recommended as a default) and other names are in widespread use. -Examples are `.hh` and `.cxx`. Use such names equivalently. In this document we refer to `.h` and `.cpp` as a shorthand for header and implementation files, even though the actual extension may be different. +Examples are `.hh`, `.C`, and `.cxx`. Use such names equivalently. +In this document, we refer to `.h` and `.cpp` as a shorthand for header and implementation files, +even though the actual extension may be different. + +Your IDE (if you use one) may have strong opinions about suffices. ##### Example @@ -15347,7 +15387,21 @@ Including entities subject to the one-definition rule leads to linkage errors. ##### Example - ??? + // file.h: + namespace Foo { + int x = 7; + int xx() { return x+x; } + } + + // file1.cpp: + #include + // ... more ... + + // file2.cpp: + #include + // ... more ... + +Linking `file1.cpp` and `file2.cpp` will give two linker errors. **Alternative formulation**: A `.h` file must contain only: @@ -15798,7 +15852,8 @@ A library can be statically or dynamically linked into a program, or it may be ` ##### Note -A library can contain cyclic references in the definition of its components. For example: +A library can contain cyclic references in the definition of its components. +For example: ??? @@ -15808,33 +15863,264 @@ However, a library should not depend on another that depends on it. # NR: Non-Rules and myths This section contains rules and guidelines that are popular somewhere, but that we deliberately don't recommend. -In the context of the styles of programming we recommend and support with the guidelines, these "non-rules" would do harm. +We know full well that there have been times and places where these rules made sense, and we have used them ourselves at times. +However, in the context of the styles of programming we recommend and support with the guidelines, these "non-rules" would do harm. + +Even today, there can be contexts where the rules make sense. +For example, lack of suitable tool support can make exceptions unsuitable in hard-real-time systems, +but please don't blindly trust "common wisdom" (e.g., unsupported statements about "efficiency"); +such "wisdom" may be based on decades-old information or experienced from languages with very different properties than C++ +(e.g., C or Java). + +The positive arguments for alternatives to these non-rules are listed in the rules offered as "Alternatives". Non-rule summary: -* [NR.1: All declarations should be at the top of a function](#Rnr-top) -* single-return rule -* no exceptions -* one class per source file -* two-phase initialization -* goto exit -* make all data members `protected` +* [NR.1: Don't: All declarations should be at the top of a function](#Rnr-top) +* [NR.2: Don't: Have only a single single `return`-statement in a function](#Rnr-single-return) +* [NR.3: Don't: Don't use exceptions](#Rnr-no-exceptions) +* [NR.4: Don't: Place each class declaration in its own source file](#Rnr-lots-of-files) +* [NR.5: Don't: Don't do substantive work in a constructor; instead use two-phase initialization](#Rnr-two-phase-init) +* [NR.6: Don't: Place all cleanup actions at the end of a fucntion and `goto exit`](#Rnr-goto-exit) +* [NR.7: Don't: Make all data members `protected`](#Rnr-protected-data) * ??? -### NR.1: All declarations should be at the top of a function +### NR.1: Don't: All declarations should be at the top of a function -##### Reason +##### Reason (not to follow this rule) This rule is a legacy of old programming languages that didn't allow initialization of variables and constants after a statement. This leads to longer programs and more errors caused by uninitialized and wrongly initialized variables. -##### Alternative +##### Example, bad -Instead: + ??? + +The larger the distance between the uninitialized variable and its use, the larger the chance of a bug. +Fortunately, compilers catch many "used before set" errors. + + +##### Alternative * [Always initialize an object](#Res-always) * [ES.21: Don't introduce a variable (or constant) before you need to use it](#Res-introduce) +### NR.2: Don't: Have only a single single `return`-statement in a function + +##### Reason (not to follow this rule) + +The single-return rule can lead to unnecessarily convoluted code and the introduction of extra state variables. +In particular, the single-return rule makes it harder to concentrate error checking at the top of a function. + +##### Example + + template + // requires Number + string sign(T x) + { + if (x<0) + return "negative"; + else if (x>0) + return "positive"; + return "zero"; + } + +to use a single return only we would have to do something like + + template + // requires Number + string sign(T x) // bad + { + string res; + if (x<0) + res = "negative"; + else if (x>0) + res = "positive"; + else + res ="zero"; + return res; + } + +This is both longer and likely to be less efficient. +The larger and more compliciated the function is, the more painful the workarounds get. +Of course many simple functions will natually have just one `return` because of their simpler inherent logic. + +##### Example + + int index(const char* p) + { + if (p==nullptr) return -1; // error indicator: alternatively `throw nullptr_error{}` + // ... do a lookup to find the index for p + return i; + } + +If we applied the rule, we'd get something like + + int index2(const char* p) + { + int i; + if (p==nullptr) + i = -1; // error indicator + else { + // ... do a lookup to find the index for p + } + return i; + } + +Note that we (deliberately) violated the rule against uninitialized variables because this style commonly leads to that. +Also, this style is a temptation to use the [goto exit](#Rnr-goto-exit) non-rule. + +##### Alternative + +* Keep functions short and simple +* Feel free to use multiple `return` statements (and to throw exceptions). + +### NR.3: Don't: Don't use exceptions + +##### Reason (not to follow this rule) + +There seem to be three main reasons given for this non-rule: + +* exceptions are inefficient +* exceptions lead to leaks and errors +* exception performance is not predictable + +There is no way we can settle this issue to the satisfaction of everybody. +After all, the discussions about exceptions have been going on for 40+ years. +Some languages cannot be used without exceptions, but others do not support them. +This leads to strong traditions for the use and non-use of exceptions, and to heated debates. + +However, we can briefly outline why we consider exceptions the best alternative for general-purpose programming +and in the context of these guidelines. +Simple arguments for and against are often inconclusive. +There are specialized applications where exceptions indeed can be inappropriate +(e.g., hard-real time systems without support for reliable estimates of the cost of handling an exception). + +Consider the major objections to exceptions in turn + +* Exceptions are inefficient: +Compared to what? +When comparing make sure that the same set of errors are handled and that they are handled equivalently. +In particular, do not compare a program that immediately terminate on seeing an error with a program +that carefully cleans up resources before loging an error. +Yes, some systems have poor exception handling implementations; sometimes, such implementations force us to use +other error-handling approaches, but that's not a fundamental problem with exceptions. +When using an efficiency argument - in any context - be careful that you have good data that actually provides +insight into the problem under discussion. +* Exceptions lead to leaks and errors. +They do not. +If your program is a rat's nest of pointers without an overall strategy for resource management, +you have a problem whatever you do. +If your system consists of a million lines of such code, +you probably will not be able to use exceptions, +but that's a problem with excessive and undisciplined pointer use, rather than with exceptions. +In our opinion, you need RAII to make exception-based error handling simple and safe -- simpler and safer than alternatives. +* Exception performance is not predictable +If you are in a hard-real-time system where you must guarantee completion of a task in a given time, +you need tools to back up such guarantees. +As far as we know such tools are not available (at least not to most programmers). + +Many, possibly most, problems with exceptions stem from historical needs to interact with messy old code. + +The fundamental arguments for the use of exceptions are + +* They clearly separates error return from ordinary return +* They cannot be forgotten or ignored +* They can be used systematically + +Remember + +* Exceptions are for reporting errors (in C++; other languages can have different uses for exceptions). +* Exceptions are not for errors that can be handled locally. +* Don't try to catch every exception in every function (that's tedious, clumsy, and leads to slow code). +* Exceptions are not for errors that require instant termination of a module/system after a non-recoverable error. + +##### Example + + ??? + +##### Alternative + +* [RAII](#Re-raii) +* Contracts/assertions: Use GSL's `Expects` and `Ensures` (until we get language support for contracts) + +### NR.4: Don't: Place each class declaration in its own source file + +##### Reason (not to follow this rule) + +The resulting number of files are hard to manage and can slow down compilation. +Individual classes are rarely a good logical unit of maintenance and distribution. + +##### Example + + ??? + +##### Alternative + +* Use namespaces containing logically cohesive sets of classes and functions. + +### NR.5: Don't: Don't do substantive work in a constructor; instead use two-phase initialization + +##### Reason (not to follow this rule) + +Folloing this rule leads to weaker invariants, +more complicated code (having to deal with semi-constructed objects), +and errors (when we didn't deal correctly with semi-constructed objects consistently). + +##### Example + + ??? + +##### Alternative + +* Always establish a class invariant in a constructor. +* Don't define an object before it is needed. + +### NR.6: Don't: Place all cleanup actions at the end of a fucntion and `goto exit` + +##### Reason (not to follow this rule) + +`goto` is error-prone. +This technique is a pre-exception technique for RAII-like resource and error handling. + +##### Example, bad + + void do_something(int n) + { + if (n<100) goto exit; + // ... + int* p = (int*)malloc(n); + // ... + if (some_ error) goto_exit; + // ... + exit: + free(p); + } + +and spot the bug. + +##### Alternative + +* Use exceptions and [RAII](#Re-raii) +* for non-RAII resources, use [`finally`](#Re-finally). + +### NR.7: Don't: Make all data members `protected` + +##### Reason (not to follow this rule) + +`protected` data is a source of errors. +`protected` data can be manipulated from an unbounded amount of code in various places. +`protected` data is the class hierarchy equivalent to global data. + +##### Example + + ??? + +##### Alternative + +* [M]ake member data `public` or (preferably) `private`](#Rh-protected) + + # RF: References Many coding standards, rules, and guidelines have been written for C++, and especially for specialized uses of C++.