Exceptions - On how specific/nonspecific to make them

Started by
14 comments, last by Rattrap 18 years, 9 months ago
Let's say we have a Sprite class that loads a sprite file in the constructor. In constructing the Sprite, there could a number of things that go wrong, such as the file not existing, an invalid transparency value, invalid number of frames value, or invalid animation time between frames value. Should any of these problems occur, an exception should be thrown so that the code calling it can identify the problem and act appropiately. I pondered two possibilities for "naming" the exceptions: Note: In both cases the exceptions would be derived from std::exception 1. Have NonexistentFile, InvalidTransparency, InvalidNumberOfFrames, etc structs. In other words, have a separate exception struct for each possible type of error. The external code then, has to have a catch() block for every type of error to handle it. 2. Have one SpriteException class that will accept a std::string message and store it internally. A call to the overrided member function what() will then return that message. The different types of errors would all throw a SpriteException, but pass in their own unique string of what the problem is so the type of problem can be identifier. For example: throw SpriteException("File does not exist") or throw Sprite Exception("Invalid Transparency"); And then the external code that calls it has a single catch(SpriteException) that then determines via a bunch of if/else if statements and what() the specific problem. Any thoughts on which approach to take? Oh, and another related question: Should the specific exception structs for classes like these be place within the class itself? I suppose it would be either that or within the same namespace as the class.
Advertisement
Wrap up your file-system access and have it throw the FileNotFound exception and others (determine how specific you want to get, although, for a system level thing like this, specific == good). For the sprite, throw a generic SpriteException with a string describing the problem. At least, that's how I would do it.

In time the project grows, the ignorance of its devs it shows, with many a convoluted function, it plunges into deep compunction, the price of failure is high, Washu's mirth is nigh.

I'll describe my own situation. My generic serialization library can have any number of bad things happen that generate an exception (broken streams, missing files, bad checksums, missing resources, etc, etc), but there is only one thing that is going to be done with the exception - and that is - indicate that loading failed and report the error message (with details of what was being done at the time).

Because there isn't much to do besides report an error message - there is no reason to mess around making various exception classes (Yag Ni, Folks). The actual use of the thing looks like this:

// the throw:throw ASerializeError(serializer) << "Error doing XYZ in " << something << ".";// the catchcatch(ASerializeError& e){    e.Log();    return false; // indicate to the caller that loading failed}


So yes - unless you actually have something you can do with the various kinds of error classes - just make one (otherwise you're probably wasting effort).

If it turns out you need more classes later for more specific catching behaviour - it is fairly trivial to add more classes that inherit from your main exception class.
Just want to mention two things [smile].


  • C++ iostreams can throw exceptions, just turn them on with std::basic_ios::exceptions set with std::ios_base::iostate

  • its not necessary to have user-defined exception types be sub-types of std::exception, infact if i remember correctly Bjarne's recommends not to derive from it (not that there is anything particularly wrong by doing it) and have your own base exception type if you wish

When I use custom exceptions, I use a base exception class like CException. It usually takes 4 parameters. The Exception name, the File it was thrown from, the line it was thrown from (this ends up being the throw command, but it at least points you in the right direction), and then a custom message field. I just derive a all of my other exceptions from that CException class, so specializations can be made, but ultimately I just need to catch CException to get them. What is also nice is with a little bit of work, you can even build an Exception that will catch Windows Structured exceptions (good for debugging, but I definately wouldn't leave these on in a real release).

"I can't believe I'm defending logic to a turing machine." - Kent Woolworth [Other Space]

Thanks for the replies. Route #2 it is.

Quote:Original post by snk_kid

  • its not necessary to have user-defined exception types be sub-types of std::exception, infact if i remember correctly Bjarne's recommends not to derive from it (not that there is anything particularly wrong by doing it) and have your own base exception type if you wish



He recommends against it? I remember reading a few articles that recommended for it. Something about catch(...) being bad for some reason and catch(std::exception& e) being a better solution which would work if all your exceptions were derived from std::exception.
catch(...) should be the last thing, if you have a list of catches, its the catch all.

I usually have the following in my main loop

try{  // some code}catch(const CException& ex) // See my post above{  // Print information from the exception}catch(...){  // Print that an unknown exception occured.}


[edit]

Also note the use of catching an exception by reference. This is a little more efficient. In C++ everything is an exception, you can throw ints, chars, any data type. I try to control what gets thrown in my code (a few try/catch blocks that catch exceptions and then throw one of my custom ones).

"I can't believe I'm defending logic to a turing machine." - Kent Woolworth [Other Space]

Quote:Original post by Rattrap

Also note the use of catching an exception by reference. This is a little more efficient.


If I'm not mistaken, you actually need to use reference, since the exception might be a subtype of CException (or whatever you use).

I personally derive most of my exceptions from std::runtime_error, since it comes with a constructor that takes a string for what() (so i don't have to write it my self. Call me lazy. ;) Or I just use runtime_error or logic_error as such. Making a utility exception class that takes the linenumber and file name sounds like a good idea. It might be worth it to write a macro for throws:


#define THROW(E,M) (throw E(M,__FILE__,__LINE__))

Or something like that.
You don't HAVE to catch by reference (as it were), you cold just do

catch(CException ex)

The difference being it is going to call the copy constructor to pass ex to the code block. Passing by reference just prevents a copy from occuring.

[edit]
Quote:Original post by FlowingOoze
Quote:Original post by Rattrap

Also note the use of catching an exception by reference. This is a little more efficient.


If I'm not mistaken, you actually need to use reference, since the exception might be a subtype of CException (or whatever you use).

I personally derive most of my exceptions from std::runtime_error, since it comes with a constructor that takes a string for what() (so i don't have to write it my self. Call me lazy. ;) Or I just use runtime_error or logic_error as such. Making a utility exception class that takes the linenumber and file name sounds like a good idea. It might be worth it to write a macro for throws:


#define THROW(E,M) (throw E(M,__FILE__,__LINE__))

Or something like that.


And how your doing it is almost identical to the way I do it, I just made my own base class and derived clases.

"I can't believe I'm defending logic to a turing machine." - Kent Woolworth [Other Space]

Quote:Original post by Rattrap
You don't HAVE to catch by reference (as it were), you cold just do

catch(CException ex)

The difference being it is going to call the copy constructor to pass ex to the code block. Passing by reference just prevents a copy from occuring.


If you're lucky enough to have a copy constructor that will work properly for subtypes (that may override what()). Otherwise you're in a mess and better off using reference.

This topic is closed to new replies.

Advertisement