Skip to content

Commit a5cd446

Browse files
minor cleanup of exception section
Closing issue 313 on E.25 for now
1 parent 441070c commit a5cd446

File tree

1 file changed

+81
-18
lines changed

1 file changed

+81
-18
lines changed

CppCoreGuidelines.md

Lines changed: 81 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -12041,7 +12041,7 @@ Error-handling rule summary:
1204112041
* [E.18: Minimize the use of explicit `try`/`catch`](#Re-catch)
1204212042
* [E.19: Use a `final_action` object to express cleanup if no suitable resource handle is available](#Re-finally)
1204312043

12044-
* [E.25: If you can't throw exceptions, simulate RAII for resource management](Re-no-throw-raii)
12044+
* [E.25: If you can't throw exceptions, simulate RAII for resource management](#Re-no-throw-raii)
1204512045
* [E.26: If you can't throw exceptions, consider failing fast](#Re-no-throw-crash)
1204612046
* [E.27: If you can't throw exceptions, use error codes systematically](#Re-no-throw-codes)
1204712047
* [E.28: Avoid error handling based on global state (e.g. `errno`)](#Re-no-throw)
@@ -12113,7 +12113,9 @@ Don't use a `throw` as simply an alternative way of returning a value from a fun
1211312113

1211412114
##### Note
1211512115

12116-
Before deciding that you cannot afford or don't like exception-based error handling, have a look at the [alternatives](#Re-no-throw-raii).
12116+
Before deciding that you cannot afford or don't like exception-based error handling, have a look at the [alternatives](#Re-no-throw-raii);
12117+
they have their own complexities and problems.
12118+
Also, as far as possible, measure before making claims about efficiency.
1211712119

1211812120
### <a name="Re-errors"></a>E.3: Use exceptions for error handling only
1211912121

@@ -12139,6 +12141,11 @@ C++ implementations tend to be optimized based on the assumption that exceptions
1213912141
This is more complicated and most likely runs much slower than the obvious alternative.
1214012142
There is nothing exceptional about finding a value in a `vector`.
1214112143

12144+
##### Enforcement
12145+
12146+
Would need to be heuristic.
12147+
Look for exception values "leaked" out of `catch` clauses.
12148+
1214212149
### <a name="Re-design-invariants"></a>E.4: Design your error-handling strategy around invariants
1214312150

1214412151
##### Reason
@@ -12149,6 +12156,10 @@ To use an object it must be in a valid state (defined formally or informally by
1214912156

1215012157
An [invariant](#Rc-struct) is logical condition for the members of an object that a constructor must establish for the public member functions to assume.
1215112158

12159+
##### Enforcement
12160+
12161+
???
12162+
1215212163
### <a name="Re-invariant"></a>E.5: Let a constructor establish an invariant, and throw if it cannot
1215312164

1215412165
##### Reason
@@ -12158,13 +12169,29 @@ Not all member functions can be called.
1215812169

1215912170
##### Example
1216012171

12161-
???
12172+
class vector { // very simplified vector of doubles
12173+
// if elem!=nullptr then elem points to sz doubles
12174+
public:
12175+
vector() : elem{nullptr}, sz{0}{}
12176+
vctor(int s) : elem{new double},sz{s} { /* initialize elements */ }
12177+
~vector() { delete elem; }
12178+
12179+
double& operator[](int s) { return elem[s]; }
12180+
// ...
12181+
private:
12182+
owner<double*> elem;
12183+
int sz;
12184+
}
12185+
12186+
The class invariant - here stated as a comment - is established by the constructors.
12187+
`new` throws if it cannot allocate the required memory.
12188+
The operators, notably the subscript operator, relies on the invariant.
1216212189

1216312190
**See also**: [If a constructor cannot construct a valid object, throw an exception](#Rc-throw)
1216412191

1216512192
##### Enforcement
1216612193

12167-
???
12194+
Flag classes with `private` state witout a constructor (public, protected, or private).
1216812195

1216912196
### <a name="Re-raii"></a>E.6: Use RAII to prevent leaks
1217012197

@@ -12239,6 +12266,9 @@ We know of only a few good reasons:
1223912266
* We are in a hard-real-time system and we don't have tools that guarantee us that an exception is handled within the required time.
1224012267
* We are in a system with tons of legacy code using lots of pointers in difficult-to-understand ways
1224112268
(in particular without a recognizable ownership strategy) so that exceptions could cause leaks.
12269+
* Our implemention of the C++ exeption mechanisms is unreasonably poor
12270+
(slow, memory consuming, failing to work correctly for dynamically linked libraries, etc.).
12271+
Complain to your implementation purveyer; if no user complains, no improvement will happen.
1224212272
* We get fired if we challenge our manager's ancient wisdom.
1224312273

1224412274
Only the first of these reasons is fundamental, so whenever possible, use exceptions to implement RAII, or design your RAII objects to never fail.
@@ -12264,7 +12294,7 @@ One strategy is to add a `valid()` operation to every resource handle:
1226412294
Obviously, this increases the size of the code, doesn't allow for implicit propagation of "exceptions" (`valid()` checks), and `valid()` checks can be forgotten.
1226512295
Prefer to use exceptions.
1226612296

12267-
**See also**: [discussion](#Sd-noexcept).
12297+
**See also**: [Use of `noexcept`](#Se-noexcept).
1226812298

1226912299
##### Enforcement
1227012300

@@ -12299,7 +12329,8 @@ To make error handling systematic, robust, and efficient.
1229912329
return log(sqrt(d <= 0 ? 1 : d));
1230012330
}
1230112331

12302-
Here, I know that `compute` will not throw because it is composed out of operations that don't throw. By declaring `compute` to be `noexcept` I give the compiler and human readers information that can make it easier for them to understand and manipulate `compute`.
12332+
Here, we know that `compute` will not throw because it is composed out of operations that don't throw.
12333+
By declaring `compute` to be `noexcept`, we give the compiler and human readers information that can make it easier for them to understand and manipulate `compute`.
1230312334

1230412335
##### Note
1230512336

@@ -12343,6 +12374,14 @@ One way of avoiding such problems is to use resource handles consistently:
1234312374
// no need for delete p
1234412375
}
1234512376

12377+
Another solution (often better) would be to use a local variable to eliminate explicit use of pointers:
12378+
12379+
void no_leak(_simplified(int x)
12380+
{
12381+
vector<int> v(7);
12382+
// ...
12383+
}
12384+
1234612385
**See also**: ???resource rule ???
1234712386

1234812387
### <a name="Re-exception-types"></a>E.14: Use purpose-designed user-defined types as exceptions (not built-in types)
@@ -12468,19 +12507,27 @@ We don't know how to write reliable programs if a destructor, a swap, or a memor
1246812507

1246912508
##### Note
1247012509

12471-
Many have tried to write reliable code violating this rule for examples such as a network connection that "refuses to close". To the best of our knowledge nobody has found a general way of doing this though occasionally, for very specific examples, you can get away with setting some state for future cleanup. Every example we have seen of this is error-prone, specialized, and usually buggy.
12510+
Many have tried to write reliable code violating this rule for examples, such as a network connection that "refuses to close".
12511+
To the best of our knowledge nobody has found a general way of doing this.
12512+
Occasionally, for very specific examples, you can get away with setting some state for future cleanup.
12513+
For example, we might put a socket that does not want to close on a "bad socket" list,
12514+
to be examined by a regular sweep of the system state.
12515+
Every example we have seen of this is error-prone, specialized, and often buggy.
1247212516

1247312517
##### Note
1247412518

1247512519
The standard library assumes that destructors, deallocation functions (e.g., `operator delete`), and `swap` do not throw. If they do, basic standard library invariants are broken.
1247612520

1247712521
##### Note
1247812522

12479-
Deallocation functions, including `operator delete`, must be `noexcept`. `swap` functions must be `noexcept`. Most destructors are implicitly `noexcept` by default.
12523+
Deallocation functions, including `operator delete`, must be `noexcept`. `swap` functions must be `noexcept`.
12524+
Most destructors are implicitly `noexcept` by default.
12525+
Also, [make move operations `noexcept`](##Rc-move-noexcept).
1248012526

1248112527
##### Enforcement
1248212528

12483-
Catch destructors, deallocation operations, and `swap`s that `throw`. Catch such operations that are not `noexcept`.
12529+
Catch destructors, deallocation operations, and `swap`s that `throw`.
12530+
Catch such operations that are not `noexcept`.
1248412531

1248512532
**See also**: [discussion](#Sd-never-fail)
1248612533

@@ -12500,6 +12547,7 @@ Let cleanup actions on the unwinding path be handled by [RAII](#Re-raii).
1250012547
// ...
1250112548
}
1250212549
catch (...) {
12550+
// no action
1250312551
throw; // propagate exception
1250412552
}
1250512553
}
@@ -12524,6 +12572,7 @@ Let cleanup actions on the unwinding path be handled by [RAII](#Re-raii).
1252412572
try {
1252512573
p = new Gadget(s);
1252612574
// ...
12575+
delete p;
1252712576
}
1252812577
catch (Gadget_construction_failure) {
1252912578
delete p;
@@ -12571,6 +12620,15 @@ Better:
1257112620
`finally` is not as messy as `try`/`catch`, but it is still ad-hoc.
1257212621
Prefer [proper resource management objects](#Re-raii).
1257312622

12623+
###### Note
12624+
12625+
Use of `finally` is a systematic and reasonably clean alternative to the old [`goto exit;` technique](##Re-no-throw-codes)
12626+
for dealing with cleanup where resource management is not systematic.
12627+
12628+
###### Enforcement
12629+
12630+
Heuristic: Detect `goto exit;`
12631+
1257412632
### <a name="Re-no-throw-raii"></a>E.25: If you can't throw exceptions, simulate RAII for resource management
1257512633

1257612634
##### Reason
@@ -12600,23 +12658,25 @@ or have such a rat's nest of old-style code
1260012658
that it is infeasible to introduce simple and systematic exception handling.
1260112659

1260212660
Before condemning exceptions or complaining too much about their cost, consider examples of the use of [error codes](#Re-no-throw-codes).
12661+
Consider the cost and complexity of the use of error codes.
12662+
If performence is your worry, measure.
1260312663

1260412664
##### Example
1260512665

1260612666
Assume you wanted to write
1260712667

12608-
void func(int n)
12668+
void func(zstring arg)
1260912669
{
12610-
Gadget g(n);
12670+
Gadget g {arg};
1261112671
// ...
1261212672
}
1261312673

1261412674
If the `gadget` isn't correctly constructed, `func` exits with an exception.
1261512675
If we cannot throw an exception, we can simulate this RAII style of resource handling by adding a `valid()` member function to `Gadget`:
1261612676

12617-
error_indicator func(int n)
12677+
error_indicator func(zstring arg)
1261812678
{
12619-
Gadget g(n);
12679+
Gadget g {arg};
1262012680
if (!g.valid()) return gadget_construction_error;
1262112681
// ...
1262212682
return 0; // zero indicates "good"
@@ -12658,7 +12718,7 @@ In such cases, "crashing" is simply leaving error handling to the next level of
1265812718
// ...
1265912719
}
1266012720

12661-
Most systems cannot handle memory exhaustion gracefully anyway. This is roughly equivalent to
12721+
Most programs cannot handle memory exhaustion gracefully anyway. This is roughly equivalent to
1266212722

1266312723
void f(Int n)
1266412724
{
@@ -12811,19 +12871,22 @@ A not uncommon technique is to gather cleanup at the end of the function to avoi
1281112871
}
1281212872
// ...
1281312873

12814-
exit:
12815-
if (g1.valid()) cleanup(g1);
12816-
if (g1.valid()) cleanup(g2);
12817-
return {res, err};
12874+
exit:
12875+
if (g1.valid()) cleanup(g1);
12876+
if (g1.valid()) cleanup(g2);
12877+
return {res, err};
1281812878
}
1281912879

1282012880
The larger the function, the more tempting this technique becomes.
12881+
`finally` can [ease the pain a bit](#Re-finally).
1282112882
Also, the larger the program becomes the harder it is to apply an error-indicator-based error handling strategy systematically.
1282212883

1282312884
We [prefer exception-based error handling](#Re-throw) and recommend [keeping functions short](#Rf-single).
1282412885

1282512886
**See also**: [Discussion](#Sd-???).
1282612887

12888+
**See also**: [Returning multiple values](#Rf-out-multi).
12889+
1282712890
##### Enforcement
1282812891

1282912892
Awkward.

0 commit comments

Comments
 (0)