Show Lecture.Old-fashionedErrorHandling as a slide show.
CS253 Old-fashioned Error Handling
The Old Days
- Before try/recover, throw/catch, and the like became popular,
many other methods of error handling were used.
- Many are still used.
- We’ll look at several old techniques, paying attention to
whether objects get properly destroyed.
- These examples use my
Loud
class,
which announces constructors & destructors
Out-of-band value
- For a function that normally returns a value, as opposed to a mere
success/failure status indication, a popular technique is to return an
“out-of-band” value to indicate an error, also called called a
sentinel value.
- This value must not be one of the normal, non-error, return values.
- Examples:
- A
locate()
function that returns a pointer to some datum might
return nullptr to indicate a failure.
- An integer square root function could return −1 to indicate that you
attempted to take the square root of a negative number.
- A floating-point square root function could return NAN
(Not-A-Number) to indicate that you attempted to take the
square root of a negative number.
Out-of-band value
Loud a='a';
double invert(double num) {
Loud b('b');
if (num == 0)
return INFINITY;
return 1/num;
}
int main() {
Loud c('c');
double result = invert(0);
if (result == INFINITY)
return 1;
cout << result << '\n';
}
Loud::Loud() [c='a']
Loud::Loud() [c='c']
Loud::Loud() [c='b']
Loud::~Loud() [c='b']
Loud::~Loud() [c='c']
Loud::~Loud() [c='a']
- Did all the objects get properly cleaned up?
That works, but …
- That works. However:
- You need an out-of-band value. This isn’t always possible.
- The caller of
foo()
needs to check for errors, and return early.
- So does his caller.
- So does his caller.
- …
Unacceptable out-of-band values
Remember that nullptr and NULL aren’t magic (and null
doesn’t exist).
You can only return a null pointer if you’re returning a pointer.
It won’t work for returning a number, object, or reference.
double foo() {
return nullptr;
}
int main() {
cout << foo();
}
c.cc:2: error: cannot convert ‘std::nullptr_t’ to ‘double’ in return
For a floating-point value, NAN (not a number) is often
a good sentinel value.
Unacceptable out-of-band values
For an int, nullptr isn’t possible, and NAN is only for
floating-point values:
int bar() {
return nullptr;
}
int main() {
cout << bar();
}
c.cc:2: error: cannot convert ‘std::nullptr_t’ to ‘int’ in return
A good sentinel depends on the possible return values. -1
is fine
for an integer square root routine, and a billion works for historical
years.
Unacceptable out-of-band values
This compiles, because string has a ctor that takes a const char *,
and nullptr converts to that. However, it fails at runtime:
string baz() {
return nullptr;
}
int main() {
cout << baz();
}
terminate called after throwing an instance of 'std::logic_error'
what(): basic_string::_M_construct null not valid
SIGABRT: Aborted
For a string, ""
is often a good sentinel value.
Otherwise pick something that can’t occur in the data,
e.g., "//**NOT POSSIBLE**//"
.
exit
Loud a='a';
double invert(double num) {
Loud b('b');
if (num == 0)
exit(1);
return 1/num;
}
int main() {
Loud c('c');
cout << invert(0) << '\n';
}
Loud::Loud() [c='a']
Loud::Loud() [c='c']
Loud::Loud() [c='b']
Loud::~Loud() [c='a']
- That was brutal.
- Dtor was called for global object, but not for local objects.
abort
Loud a='a';
double invert(double num) {
Loud b('b');
if (num == 0)
abort();
return 1/num;
}
int main() {
Loud c('c');
cout << invert(0) << '\n';
}
Loud::Loud() [c='a']
Loud::Loud() [c='c']
Loud::Loud() [c='b']
SIGABRT: Aborted
- Worse: no dtors called at all.
- abort() sent the SIGABRT (abnormal termation) signal,
similar to SIGINT (keyboard interrupt).
assert
Loud a='a';
double invert(double num) {
Loud b('b');
assert(num);
return 1/num;
}
int main() {
Loud c('c');
cout << invert(0) << '\n';
}
Loud::Loud() [c='a']
Loud::Loud() [c='c']
Loud::Loud() [c='b']
a.out: c.cc:5: double invert(double): Assertion `num' failed.
SIGABRT: Aborted
- I suppose that we have low expectations for a failed assertion.
- assert() calls abort(), so similar results.
Division by Zero
Loud a='a';
static int z = 0;
double invert(double num) {
Loud b('b');
if (num == 0)
z = 1/z;
return 1/num;
}
int main() {
Loud c('c');
cout << invert(0) << '\n';
}
Loud::Loud() [c='a']
Loud::Loud() [c='c']
Loud::Loud() [c='b']
SIGFPE: Floating point exception
- Division by zero is undefined behavior. On this computer, it
generates the SIGFPE (floating-point exception) signal.
- Since it was a signal termination, like abort(), no dtors were called.
The Horror That Is Setjmp/Longjmp
Loud a='a';
jmp_buf env;
double invert(double num) {
Loud b('b');
if (num == 0)
longjmp(env, 456);
return 1/num;
}
int main() {
Loud c('c');
if (int val = setjmp(env)) {
cout << "error=" << val << "!\n";
return 1;
}
cout << invert(0) << '\n';
}
Loud::Loud() [c='a']
Loud::Loud() [c='c']
Loud::Loud() [c='b']
error=456!
Loud::~Loud() [c='c']
Loud::~Loud() [c='a']
- Lord, take me now—I done seen everything!