Advertisement

To use exceptions or not to use exceptions

Started by September 25, 2008 03:17 PM
7 comments, last by ValMan 16 years, 4 months ago
Hi, I'm thinking of a good way to implement exception handling in a few of my wrapper classes. The way I remember it's done is with try/catch statements. I just wanted to see if these are used in professional projects and whether I should use them. My alternate approach is simply doing something like Windows or DirectX and encoding the error into a DWORD and returnining it with every function call. I would then have a function that would read that DWORD and output a meaningfull string. Which of the two methods are more popular/more efficient or simply more versatile. (Or maybe there are other methods I'm unaware of) Thanks.

You didn't come into this world. You came out of it, like a wave from the ocean. You are not a stranger here. -Alan Watts

It's rarely a good idea to use one approach or the other exclusively, both exceptions and return codes have different semantics and different situations in which they are the more appropriate choice. Exceptions, for example, are generally only used in C++ for failures that are not expected to happen (e.g., are "exceptional").

Using exceptions properly is more than just littering your code with try blocks, as well, its worth noting. There are many resources available online discussion exception safety and such.

Finally, exceptions are certainly used in professional projects, however the extent to which they are used varies, often because of technical or cultural reasons -- some of these reasons are good, some of them are bad or based on misinformation.
Advertisement
I would generally recommend using exception handling.

The positive side is that your code becomes more self-explanatory, as only the functions that actually have something to return will return a value, and the "action" functions can be voids. Another positive thing is that you won't have to constantly check for return value failures, which is something that makes your code longer and less readable.

The negative side, at least in my experience, is that you would have to derive all your error codes from the std::exception class (to keep your structure sort of standard-compatible), and if you have a lot of errors, you will end up with lots and lots of code.

My error handling scheme involves writing a class for each "error code", and some of those classes have custom members. For example ErrorFileOpen has file path member (inherited). ErrorFileParse has file path, line number and column number. Both are derived from ErrorFile which has file path member. I then have a global error stack to which the errors get pushed at the time they are thrown - that way Stream::Read can throw ErrorFileRead and then the caller, XMLFile::Deserialize for example, can throw ErrorFileRead, and then InventoryItem::Deserialize can throw ErrorFileRead, etc. By the time all the functions return, the final exception throw will get caught either by a console command (a static function inside my Game class), or by the WinMain where game loop is. And then I can dump my error stack to find out where original error occured, and how it led up to the failure. If the exception gets caught inside a console command, I just dump the error stack to the console. If it made its way to the game loop, I exit the game and display an error dialog.

But anyway, the error stack isn't accessible from my utility classes (like Stream or String), so sometimes I have to throw errors without adding them to the stack. Which at first is fine because they are all drived from Error (which is derived from std::exception). However, the callers of those utility classes that do have access to the exception stack are then responsible for adding those "stray" errors onto the stack, which requires memberwise copy of the Error-derived object. I accomplish this by having a virtual Duplicate() in the Error base class, which every single error must implement in order to be properly duplicated. The code inside that function is minimal - return new ErrorXXX(initmember1,initmember2, ...). However, I have over 200 errors defined in my engine (in the form of ErrorDirectSoundCreate, ErrorDirect3DCreate, ErrorD3DDeviceCreateTexture, ErrorD3DDeviceClear, etc). So that's a lot of code to manage, and it dirties up my class view with all those error classes.
@ValMan & jpetrie

Thanks, I see how writing an exception for every error can be time consuming, but why can't I have a more general error class such as for example a class fro every major part of directX. So for the sprite class I have XSpriteException class, in it I have a DWORD that represents what exactly failed, that can be filled out by the function.

You didn't come into this world. You came out of it, like a wave from the ocean. You are not a stranger here. -Alan Watts

Quote:

if you have a lot of errors, you will end up with lots and lots of code.

This is a fundamental 'no no' for exceptions. You shouldn't have many exception classes; you should generally only subclass an exception when you expect that client code will need to catch the exception differently and/or in different contexts. An exception, as you seem to have, for both read and write failures is not all that useful, and contributes nothing. A more generalized "IO error" exception, which perhaps a property describing why the IO operation failed, is usually better.

You should certainly not have a 1:1 mapping between error codes and exceptions. It's just cumbersome, and provides nothing usable.

For a case study, consider SlimDX. Early versions of the library featured generated exception subclasses for many failure types. This was a poor decision on our part; it complicated our code, and it forced that complication on client code as well. Users had to include multiple catch blocks per try to handle things properly... it was a mess. Now SlimDX includes exceptions broken down by subsystem: D3D9 throws one type of exception, with information about the failure baked in, and D3D10 throws another.

This is generally the direction you want to take with exceptions. Details of the failure are important, but not so important as to warrant their own type -- since with exceptions, the type determines how that exception is initially handled, but creating a slew of very specific exception types you are making decisions about how the error must be handled at a point where you do not really have that information (the point, after all, of throwing an exception from a given method is to say "I don't know how to handle this problem here,").

So try to minimize the amount of exception subclasses you generate.
Quote:
Original post by jpetrie
Quote:

if you have a lot of errors, you will end up with lots and lots of code.

This is a fundamental 'no no' for exceptions. You shouldn't have many exception classes; you should generally only subclass an exception when you expect that client code will need to catch the exception differently and/or in different contexts. An exception, as you seem to have, for both read and write failures is not all that useful, and contributes nothing. A more generalized "IO error" exception, which perhaps a property describing why the IO operation failed, is usually better.

You should certainly not have a 1:1 mapping between error codes and exceptions. It's just cumbersome, and provides nothing usable.


Thats what I was saying, but I get the point, use return values for small functions like SetMatrix() and exceptions for the potentially fatal ones, such as CreateD3D9().

You didn't come into this world. You came out of it, like a wave from the ocean. You are not a stranger here. -Alan Watts

Advertisement
Quote:

Thats what I was saying, but I get the point, use return values for small functions like SetMatrix() and exceptions for the potentially fatal ones, such as CreateD3D9().

I wasn't actually responding to anything you said, rather to the other posters comment about having lots of exception classes.
Quote:
Original post by jpetrie
Quote:

Thats what I was saying, but I get the point, use return values for small functions like SetMatrix() and exceptions for the potentially fatal ones, such as CreateD3D9().

I wasn't actually responding to anything you said, rather to the other posters comment about having lots of exception classes.


I know, I was agreeing with you [smile]

You didn't come into this world. You came out of it, like a wave from the ocean. You are not a stranger here. -Alan Watts

So what you are saying is that there should be one error class for each type of error, and to use an error code (perhaps as simple as string ID in string table) to describe a specific error. But what about extra data?

So far, my extra data goes something like this (listing only errors that have extra data):

ErrorD3DCreate: int SDKVersion
ErrorD3DGetAdapterDisplayMode: int AdapterID
ErrorD3DInvalidSurfaceFormat: D3DFORMAT Format
ErrorLoadLibrary: string LibraryPath
ErrorFreeLibrary: string LibraryPath
ErrorMultimedia: MMRESULT mmError
ErrorFileOpen: string Path
ErrorFileCreate: string Path
ErrorFileRead: string Path
ErrorFileWrite: string Path
ErrorFileSignature: string Path
ErrorFileVersion: string Path
ErrorFileNotUnicode: string Path
ErrorFileFormat: string Path
ErrorFileParse: string Path, int Line, int Column, string Expected
ErrorFileDeserialize: string Path
ErrorFileSerialize: string path
ErrorFileElement: string Path, string ElementName
ErrorFileElementFormat: string Path, string ElementName, string ExpectedFormat
ErrorMemAlloc: int Size
ErrorInvalidPtr: string VarName
ErrorInvalidIndex: string VarName
ErrorClassCreate: string ClassName
ErrorClassNotRegistered: string ClassName
ErrorInvalidTheme: string ThemePath
ErrorInvalidThemestyle: string ThemePath, string Themestyle
ErrorInvalidThemestyleElement: string ThemePath, string Themestyle, string ThemeElement

How would you deal with something like this without creating classes for errors that need extra data?

My previous scheme was for every error to have a "definition", so there would be an ErrorManager class with RegisterError function, that would take integer error number and string description. It would then parse error description for placeholders like %s and %d to figure out the number of extra data parameters and their type. Then it would allocate new ErrorDefinition object and add it to std::map, associated by integer error number. This makes it possible to have an Error class that, on construction, finds out how much extra data it needs to have (given an error number), allocates the necessary number of bytes, then proceeds to read the variable number of parameters to its constructor interpreting each according to required type.

if(*pszBuf != L'}')   ErrorManager.RaiseError(ERROR_FILE_PARSE, stream.GetPath(), stream.GetLine(), stream.GetColumn(), L"}");


But the problem is, every error will need to know about ErrorManager. I don't use singletons, so that leaves passing ErrorManager by reference into constructor and then keeping it internally. This means that low-level utility classes like String will not have any way to throw errors, unless you pass a reference to ErrorManager into every function that can throw an error, which is very unelegant.

I could not find any way around this, so I scrapped everything and created a class for every error. It seems to be working good, except that there is a lot of code (5,207 lines) and class view became hard to use. Creating new errors is very easy - copy paste an existing error of similar kind and change name. Creating new errors using definition method I did with a global LPCWSTR[] constant that lists all strings for descriptions, in the same order as the enum that lists error codes.

This topic is closed to new replies.

Advertisement