C++ FAQ Celebrating Twenty-One Years of the C++ FAQ!!!
(Click here for a personal note from Marshall Cline.)
Section 17:
[17.3] How do exceptions simplify my function return type and parameter types?

When you use return codes, you often need two or more distinct return values: one to indicate that the function succeeded and to give the computed result, and another to propagate the error information back to the caller. If there are, say, 5 ways the function could fail, you could need as many as 6 different return values: the "successful computation" return value, and a possibly different package of bits for each of the 5 error cases.

Let's simplify it down to two cases:

  • "I succeeded and the result is xxx."
  • "I failed and the error information is yyy."

Let's work a simple example: we would like to create a Number class that supports the four arithmetic operations: add, subtract, multiply and divide. This is an obvious place for overloaded operators, so let's define them:

class Number {
public:
  friend Number operator+ (Number const& x, Number const& y);
  friend Number operator- (Number const& x, Number const& y);
  friend Number operator* (Number const& x, Number const& y);
  friend Number operator/ (Number const& x, Number const& y);
  ...
};
It's very easy to use:
void f(Number x, Number y)
{
  ...
  Number sum  = x + y;
  Number diff = x - y;
  Number prod = x * y;
  Number quot = x / y;
  ...
}
But then we have a problem: handling errors. Adding numbers could cause overflow, dividing could cause divide-by-zero or underflow, etc. Whoops. How can we report both the "I succeeded and the result is xxx" as well as "I failed and the error information is yyy"?

If we use exceptions, it's easy. Think of exceptions as a separate return type that gets used only when needed. So we just define all the exceptions and throw them when needed:

void f(Number x, Number y)
{
  try {
    ...
    Number sum  = x + y;
    Number diff = x - y;
    Number prod = x * y;
    Number quot = x / y;
    ...
  }
  catch (Number::Overflow& exception) {
    ...code that handles overflow...
  }
  catch (Number::Underflow& exception) {
    ...code that handles underflow...
  }
  catch (Number::DivideByZero& exception) {
    ...code that handles divide-by-zero...
  }
}
But if we use return codes instead of exceptions, life gets hard and messy. When you can't shove both the "good" number and the error information (including details about what went wrong) inside the Number object, you will probably end up using extra by-reference parameters to handle one of the two cases: either "I succeeded" or "I failed" or both. Without loss of generality, I will handle the computed result via a normal return value and the "I failed" case via a by-reference parameter, but you can just as easily do the opposite. Here's the result:
class Number {
public:
  enum ReturnCode {
    Success,
    Overflow,
    Underflow,
    DivideByZero
  };

  Number add(Number const& y, ReturnCode& rc) const;
  Number sub(Number const& y, ReturnCode& rc) const;
  Number mul(Number const& y, ReturnCode& rc) const;
  Number div(Number const& y, ReturnCode& rc) const;
  ...
};
Now here's how to use it — this code is equivalent to the above:
int f(Number x, Number y)
{
  ...

  Number::ReturnCode rc;
  Number sum = x.add(y, rc);
  if (rc == Number::Overflow) {
    ...code that handles overflow...
    return -1;
  } else if (rc == Number::Underflow) {
    ...code that handles underflow...
    return -1;
  } else if (rc == Number::DivideByZero) {
    ...code that handles divide-by-zero...
    return -1;
  }

  Number diff = x.sub(y, rc);
  if (rc == Number::Overflow) {
    ...code that handles overflow...
    return -1;
  } else if (rc == Number::Underflow) {
    ...code that handles underflow...
    return -1;
  } else if (rc == Number::DivideByZero) {
    ...code that handles divide-by-zero...
    return -1;
  }

  Number prod = x.mul(y, rc);
  if (rc == Number::Overflow) {
    ...code that handles overflow...
    return -1;
  } else if (rc == Number::Underflow) {
    ...code that handles underflow...
    return -1;
  } else if (rc == Number::DivideByZero) {
    ...code that handles divide-by-zero...
    return -1;
  }

  Number quot = x.div(y, rc);
  if (rc == Number::Overflow) {
    ...code that handles overflow...
    return -1;
  } else if (rc == Number::Underflow) {
    ...code that handles underflow...
    return -1;
  } else if (rc == Number::DivideByZero) {
    ...code that handles divide-by-zero...
    return -1;
  }

  ...
}
The point of this is that you normally have to muck up the interface of functions that use return codes, particularly if there is more error information to propagate back to the caller. For example, if there are 5 error conditions and the "error information" requires different data structures, you might end up with a fairly messy function interface.

None of this clutter happens with exceptions. Exceptions can be thought of as a separate return value, as if the function automatically "grows" new return types and return values based on what the function can throw.

Note: Please don't write me saying that you propose using return codes and storing the error information in a global or static variable, such as Number::lastError(). That isn't thread-safe. Even if you don't have multiple threads today, you rarely want to permanently prevent anyone in the future from using your class with multiple threads. Certainly if you do, you should write lots and lots of BIG UGLY COMMENTS warning future programmers that your code is not thread-safe, and that it probably can't be made thread-safe without a substantial rewrite.