Exceptions (again)

Started by
7 comments, last by Zahlman 16 years, 9 months ago
Hiya, After scouring these forums for posts on using exceptions in C++, and asking a few questions myself, I think I finally understand how to use them correctly. I was hoping if I posted some example code here someone could let me know if my exception usage is OK, before I go off and replace all my return codes with try/catch/throws...

// Exception.h

class Exception : public std::exception { /* class declaration */ }

class SocketException : public Exception { /* class declaration */ }

// NetworkSocket.cpp

void Socket::ProcessIncomingPackets()
{
   if( SocketErrorDetected )
      throw SocketError( "Connection dropped", __LINE__ );

   // etc...
}

// WinMain()

try {

   while( true )
   {
      socket.ProcessIncomingPackets();
      DoSomethingElse();
   }

}
catch( SocketException e )
{
   game.ShowMessage( L"A network error has occured: %s.", e.what() );
   game.CloseMultiplayerGame();
   game.ShowMainMenu();
}
That's not code I'm using, I just wanted to demonstrate my current understanding. Does it look all right? Thanks for any comments or suggestions [smile]
Advertisement
Quote:
before I go off and replace all my return codes with try/catch/throws...

That would be using exceptions improperly. Exceptions are one way of communicating errors, they are not the only way, they are not always the correct way. It depends highly on context, you see.
Quote:
// NetworkSocket.cpp
This might be a situation where an exception is not useful, but it might be worthwhile to see more of the surrounding code. The question is, is the failure in question completely unexpected (probably not!), and when the failure occurs, can you handle it there? If you can handle it there, recover, or continue processing, you don't need to throw.
Quote:
// WinMain()
Wrapping the entire application in a try block isn't neccessarily all that useful, and it certainly isn't making your code "exception safe." At best it's useful as a last-ditch effort to inform you that you failed to properly handle an exception earlier. If all your exceptions are only caught at this top level, you might as well just call abort() instead of throwing them, because you're doing it wrong.

Proper use of exceptions has as much -- if not more -- to do with things that are not exceptions as it does with the exceptions themselves. You have to continue whether or not an exception is even logical (is the failure truly exceptional and unrecoverable?) and should be thrown, or if another method of reporting the error makes more sense. Then you need to consider the effect on the code at the throw site -- will resources be cleaned up, will the object be in a sane state, et cetera? Then you need to consider the code at the catch site -- the earliest place you can catch and recover that makes sense. Then you need to consider all your other code and the exception guarantees (none, strong, nothrow, etc) that code provides and whether or not it will destabilize your code and cause leaks or invalid state, et cetera.

That said, there isn't much to say about that code. It's not necessarily wrong, and its not necessarily right. It's too brief to say much.
Thanks for the reply.

Quote:Original post by jpetrie
Exceptions are one way of communicating errors, they are not the only way, they are not always the correct way. It depends highly on context, you see.


OK. Everything I read about proper exception usage seems to give different advice... what are the other ways? A lot of C++ libraries seem to just use return codes or bool returns, but I've been told this is incorrect.

Thanks again.
First off: in your exception usage you should catch exceptions by reference. Catching exceptions by value can lead to slicing. Ex:
catch( SocketException & e )


Exceptions should be used in exceptional circumstance. Running out of memory on a platform with virtual memory is an example; it's not something you expect to happen often, if at all. This is why operator new throws an exception in C++ rather than returning a null pointer.

On the other hand, not being able to open a file would not be an exceptional circumstance if you get the file name from the user or if it's on the network, etc., so using exceptions to signal that kind of error wouldn't be a good use. This is why std::fstream won't throw an exception if it can't open a file.
Quote:
OK. Everything I read about proper exception usage seems to give different advice... what are the other ways? A lot of C++ libraries seem to just use return codes or bool returns, but I've been told this is incorrect.

Basically, in the presence of a failure, you can throw, or you can return an error code. Those are pretty much your only viable options (you could also log the failure and continue working and probably crash, or fail harder down the line, but this usually doesn't really help so it's usually not considered a real option).

Now, you have to consider the overall context you're in: C++. In C++, exceptions are for really exceptional situations. In other languages and other programming paradigms, they might be used more liberally (they are apparently acceptable as means of flow control in some functional languages for example). But we're talking C++ here.

In the realm of run-time failures (compile-time failures are another matter), you have the kind of failures you can expect and the kind of failures you are pretty sure should never happen. The latter should usually stimulate exceptions, the former should probably (but not always) stimulate exceptions. What should you use to determine if you should employ exceptions or return codes in a particular failure case is whether or not the failure is valid (can it reasonably be expected to happen) and whether or not you can do something about it. If you can recover from the failure right there at the failure site there's no reason to employ any error handling at all, unless you want to return a value for informational purposes ("Hey, the device needed to be reset but we reset it, all's good now," or something).

You shouldn't simply throw exceptions for every little thing that doesn't go perfectly, you'll just write really ugly, complicated code like that. Think about how you would catch the resulting exception, think about the severity of the failure, and consider if an exception even makes sense in that context. SiCrane's examples are good.

I generally find that very deep library methods don't need to throw often, but mid-level (usually exposed) methods do because they aren't capable of handling the error and there are no sane values for them to return, or no sane way for them to do it.

Exceptions are definitely useful, but they're also hard to get completely right. I'm not saying don't use them, just don't use them without thinking about why you're using them. You should always strive to find the most elegant solution for the particular problem you're facing, and that requires thinking about the problem rather than applying dogma.
Quote: if( SocketErrorDetected )
throw SocketError( "Connection dropped", __LINE__ );


This is not exceptional condition.

On every single access to socket, you can expect it to fail.

// close the socket, we're doneif (SocketErrorDetected) {  this->close();}// Parsing the buffer is expected to failif (Parsebuffer( buffer )) {  //take action}


But...

try {  while (true) {    recv( buffer );    ProcessIncomingPackets.  }} catch (Exception e) {}socket->close();


Is valid. You catch something that cannot be handled within the socket, but it terminates any further work. You don't know where, why or how that happened, but you know you can no longer continue. bad_alloc would be an example. Or some other buffer overrun. Or exception thrown in handler.

Quote:class Exception : public std::exception { /* class declaration */ }


Redundant declaration. std::exception == Exception. There's no need to make more.
Ah OK. So in the following cases, for example...

Exceptional cases:
- Running out of memory on a PC - shouldn't happen because of paging
- No video card available - exceptional for a video game at least
- No keyboard attached
- Sound card unplugged

Non-exceptional cases
- File not found
- Rendering system unable to initialize - maybe the GPU doesnt support ps4.0 or whatever
- trying to load a corrupt mesh file

Am I getting the idea? Cases where you'd throw an exception are not expected, whereas cases where you wouldn't throw can realistically be expected to happen... Yes?
Quote:Rendering system unable to initialize - maybe the GPU doesnt support ps4.0 or whatever


Not quite.

The initialization is expected to fail. But, you don't necessarily know where, how or why? Nor do you care - once something bad happens, you cannot continue.

Quote:- Sound card unplugged


Not an exceptional condition. So you don't hear a sound, big deal.

Quote:- trying to load a corrupt mesh file


An exception would fall under initialization exception, since it would be unable to create a mesh.

But - you would only throw this for a subset of assets - not all of them. Only the crucial ones. Many textures aren't strictly required, or you can provide defaults. Sounds that fail to load can be completely ignored - they don't break the application. Player mesh failing to load - exception. UI - exception as well. Button textures, terrain textures, ambient music - non-fatal.


There is no hard rule what makes an exception. Good use of exception depends on understanding the problem, and knowing what is fatal and what isn't, or what can be recovered and what not.

In above cases, if your game is moddable, you don't throw exception when loading assets, except for the most basic ones. Everything else you just log as a failure.

Exceptions do not involve hardware failures. They involve critical errors in logic flow of the program. Why and how they are caused is irrelevant.
Quote:Original post by jpetrie
In other languages and other programming paradigms, they might be used more liberally (they are apparently acceptable as means of flow control in some functional languages for example). But we're talking C++ here.


Aside: in Python, they're actually used to implement built-in looping constructs. This is how you can have iterables that iterate over any type of thing: to indicate the end of the sequence, because no return value would make a valid sentinel (it could always be an actual element), you have to use an exception. Thus, a specific exception is provided: StopIteration.

Quote:Exceptions are definitely useful, but they're also hard to get completely right.


Quoted for emphasis.

This topic is closed to new replies.

Advertisement