You definitely want to catch and handle your errors in some way. Allowing the application to just work with a NULL pointer and crash when it tries to dereference it is going to tell you that you have a bug, but it's not going to help you find it. The same is true of letting exceptions bubble up and crash the application.
This is what I like to do. Every function that has any potential at all to cause an exception, I will enclose it in a try-catch block, add some context information to the exception for function/file/line number, then rethrow the exception so the next level function in the call stack can handle it and it's information. Then the main function, or the main thread function, will handle the exception by throwing a dialog box, writing to a log file, or both. This way you can get a lot of information about where and how the error is occurring, even in a release build.
Writing those try/catch blocks can be pretty tedious, so I use some macros to make life easier. i.e.:
#define TRYCATCH_OPEN \
try {
#define TRYCATCH_CLOSE \
} \
catch( Exception& e ) \
{ \
std::ostringstream dump; \
dump << e.message() << " " << __FUNCTION__ << "[" << __FILE__ << "(" << __LINE__ << ")]" << std::endl; \
if (e.nested() == nullptr) \
throw Exception( dump.str(), e); \
else \
throw Exception( dump.str(), *e.nested() ); \
} \
catch( std::exception& e ) \
{ \
std::ostringstream dump; \
dump << e.what() << " " << __FUNCTION__ << "[" << __FILE__ << "(" << __LINE__ << ")]" << std::endl; \
throw Exception( dump.str() ); \
}
Then when I throw my exceptions, I also use a macro so I can pin down exactly where the exception is being thrown. i.e.:
#define jp_throw(ExceptionType, ...) \
throw ExceptionType( what_(__FUNCTION__, __FILE__, __LINE__, __VA_ARGS__ ) );
Then every function/method has something like this:
void Foo::Bar()
{
TRYCATCH_OPEN
Some logic...
if (somecondition != expected)
jp_throw(SomeException, "somecondition is: ", somecondition, " expected: ", expected );
TRYCATCH_CLOSE
}
Still a little bit tedious, but much easier and cleaner than writing out all of those try/catch blocks, and it pays off.
I use custom exception classes that can contain nested exceptions, so I can preserve the original exception object while bubbling it up the chain.
This works out very well for me, and makes it much easier to track down bugs in beta and production builds without having to include debug information in the executable. The information isn't as precise as a true call stack, but it's much better than nothing.
As far as things like not being able to find a texture, I do it the way you do it, by returning a default debug texture. The only thing I do different is that I have a command-line option that can indicate that I want to throw an assertion first, so that I have the option of having the application complain loudly and definitively if there are missing resources, but also have the option of gracefully handling the situation so that development doesn't grind to a halt just because somebody broke the build with a missing resource.
Edited by krippy2k8, 04 August 2012 - 02:00 PM.