The standard approach to error handling has typically been to return an error code to the calling method. The calling method is then left with the responsibility of deciphering the returned value and acting accordingly. The return value can be as simple as a basic C or C++ type or it can be a pointer to a more robust object containing all the information necessary to fully appreciate and understand the error.
More elaborately designed error-handling techniques involve an entire error subsystem in which the called method indicates the error condition to the subsystem and then returns an error code to the caller. The caller then calls a global function exported from the error subsystem in order to determine the cause of the last error registered with the subsystem.
You can find an example of this approach in the Microsoft Open Database Connectivity (ODBC) SDK. However, regardless of the exact semantics, the basic concept remains the same: the calling method in some way calls a method and inspects the returned value to verify the relative success or failure of the called method. This approach, although being the standard for many years, is severely flawed in a number of important ways.
The next sections describe a few of the ways exception handling provides tremendous benefits over using return codes.