C++ FAQ Celebrating Twenty-One Years of the C++ FAQ!!!
(Click here for a personal note from Marshall Cline.)
Section 17:
[17.2] I'm still not convinced: a 4-line code snippet shows that return-codes aren't any worse than exceptions; why should I therefore use exceptions on an application that is orders of magnitude larger?

Because exceptions scale better than return-codes.

The problem with a 4-line example is that it has only 4 lines. Give it 4,000 lines and you'll see the difference.

Here's a classic 4-line example, first with exceptions:

try {
  f();
  ...
} catch (std::exception& e) {
  ...code that handles the error...
}
Here's the same example, this time using return-codes (rc stands for "return code"):
int rc = f();
if (rc == 0) {
  ...
} else {
  ...code that handles the error...
}
People point to those "toy" examples and say, "Exceptions don't improve coding or testing or maintenance cost in that; why should I therefore use them in a 'real' project?"

Reason: exceptions help you with real-world applications. You won't likely see much if any benefit on a toy example.

In the real world, the code that detects a problem must typically propagate error information back to a different function that will handle the problem. This "error propagation" often needs to go through dozens of functions — f1() calls f2() calls f3(), etc., and a problem is discovered way down in f10() (or f100()). The information about the problem needs to get propagated all the way back to f1(), because only f1() has enough context to actually know what should be done about the problem. In an interactive app, f1() is typically up near the main event loop, but no matter what, the code that detects the problem often isn't the same as the code that handles the problem, and the error information needs to get propagated through all the stack frames in between.

Exceptions make it easy to do this "error propagation":

void f1()
{
  try {
    ...
    f2();
    ...
  } catch (some_exception& e) {
    ...code that handles the error...
  }
}

void f2() { ...; f3(); ...; }
void f3() { ...; f4(); ...; }
void f4() { ...; f5(); ...; }
void f5() { ...; f6(); ...; }
void f6() { ...; f7(); ...; }
void f7() { ...; f8(); ...; }
void f8() { ...; f9(); ...; }
void f9() { ...; f10(); ...; }

void f10()
{
  ...
  if (...some error condition...)
    throw some_exception();
  ...
}
Only the code that detects the error, f10(), and the code that handles the error, f1(), have any clutter.

However using return-codes forces "error propagation clutter" into all the functions in between those two. Here is the equivalent code that uses return codes:

int f1()
{
  ...
  int rc = f2();
  if (rc == 0) {
    ...
  } else {
    ...code that handles the error...
  }
}

int f2()
{
  ...
  int rc = f3();
  if (rc != 0)
    return rc;
  ...
  return 0;
}

int f3()
{
  ...
  int rc = f4();
  if (rc != 0)
    return rc;
  ...
  return 0;
}

int f4()
{
  ...
  int rc = f5();
  if (rc != 0)
    return rc;
  ...
  return 0;
}

int f5()
{
  ...
  int rc = f6();
  if (rc != 0)
    return rc;
  ...
  return 0;
}

int f6()
{
  ...
  int rc = f7();
  if (rc != 0)
    return rc;
  ...
  return 0;
}

int f7()
{
  ...
  int rc = f8();
  if (rc != 0)
    return rc;
  ...
  return 0;
}

int f8()
{
  ...
  int rc = f9();
  if (rc != 0)
    return rc;
  ...
  return 0;
}

int f9()
{
  ...
  int rc = f10();
  if (rc != 0)
    return rc;
  ...
  return 0;
}

int f10()
{
  ...
  if (...some error condition...)
    return some_nonzero_error_code;
  ...
  return 0;
}
The return-code solution "spreads out" the error logic. Functions f2() through f9() have explicit, hand-written code related to propagating the error condition back up to f1(). That is badness:

If you look very narrowly at f1() and f10() in the above examples, exceptions won't give you much of an improvement. But if you instead open your eyes to the big picture, you will see a substantial difference in all the functions in between.

Conclusion: one of the benefits of exception handling is a cleaner, simpler way to propagate error information back to the caller that can handle the error. Another benefit is your function doesn't need extra machinery to propagate both the "successful" and "unsuccessful" cases back to the caller. Toy examples often don't emphasize either error propagation or handling of the two-return-types problem, therefore they don't represent Real World code.