CS253: Software Development with C++

Spring 2022

Old-fashioned Error Handling

Show Lecture.Old-fashionedErrorHandling as a slide show.

CS253 Old-fashioned Error Handling

The Old Days

Out-of-band value

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']

That works, but …

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']

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

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

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

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']