"use exceptions" v "use error codes" is never as clear-cut as examples suggest.
Use error codes for program flow. If you have an error that is expected, do not throw an exception. Eg. you're reading a file, you may throw an exception for 'file not found', 'file locked'; but never throw one for 'end of file'.
If you do, you can never write simple loops, you'll always wrapping code in exception handlers. And don't forget exceptions are very slow, this is especially important in big multi-threaded servers. (not so important at all in your desktop application).
Secondly. Be very careful with exception hierarchies. You may think its ok to have a 'Exception' class, then derive a 'NetException' from it, then 'SMTPException' for your SMTP class. But unless you hold generic data in the base class, you will always have to catch every type of exception in that hierarchy.
eg. If you put the reason for the SMTP error in your SMTPException class, you must catch it - if you only catch Exception types, you will not have access to the SMTPException members. A good workaround for this problem is to have a string and an int member in the base exception class and only use them, even for the derived types. Unfortunately std::exception only offers a string :(
Some people say that doing this means you might as well only have a single exception type, especially as you will always catch the base class type anyway.
If you do use exceptions you must take the trouble to populate them with more data than you would with an error code. With errors, you must handle them immediately or they get lost in the code. With an exception, it may get caught many levels away from where it was thrown - like in Roddy's example. DoC is called, and gets an exception 2 levels in from DoA. Unless you specify the error to be specific to the code in DoA, you may think it was thrown from the DoB function. (simple example, but I've seen code where an exception was handled many levels down the call stack. It was a b*st*rd to debug. This especially applies to OO programs)
So hopefully, I've given you enough to think about. The simple truth of the matter is that style means nothing in error handling, practicality is everything. If you have to put log statements everywhere an error can occur, then do so. It matters a lot more that you can see where the code went wrong (and what data was being worked with) than you have a elegant exception hierarchy or you've littered your code with exception handlers. If you cannot easily trace the error, your error handling code is useless.
Exceptions are good, use them. But think about what you're doing, do not misuse or overuse them. A misused exception is worse than no error handling at all (as you can grab a crashdump and view the unhandled exception to find the error in seconds. With an exception that is eaten and ignored, you're stuffed).
I've found over the years that the biggest assistant to debugging is logging. Write logs, write lots of logs.