Thread: Error Handling in Constructor

  1. #16
    Registered User
    Join Date
    May 2003
    Posts
    1,619
    Here are examples of good ways of using try/catch:

    1. Say you are opening a file and then reading structures out of it, which you're putting into a std::map.

    You need ONE try...catch statement, which can deal with any of the following:
    * File can't be opened
    * File reading failed
    * Memory allocation failed
    * A duplicate structure ID already exists in the map
    etc., because in general, the overall program's response should be the same to any of these errors: give an error message (which you put in your exception), cleanup (which is done automatically as the exception propagates) and then do whatever you do when a file can't be opened (which is what happens in your catch).

    Another example would be an application that needed to download information from a webpage. You can have many error conditions:
    * Can't open a socket
    * Can't connect to remote server
    * No response from remote server
    * Response from remote server is incorrect
    etc.

    Again, a single try, a single catch. All cleanup is automatic.
    Last edited by Cat; 08-30-2006 at 06:08 PM.
    You ever try a pink golf ball, Wally? Why, the wind shear on a pink ball alone can take the head clean off a 90 pound midget at 300 yards.

  2. #17
    Cat without Hat CornedBee's Avatar
    Join Date
    Apr 2003
    Posts
    8,895
    Quote Originally Posted by Hikaru
    fstreams use the flag, and I can't think of a C++ class that throws an exception so a flag sounds like the best way.
    string, vector, and all the other containers with the default allocator throw an exception in the constructor if they can't allocate enough memory.
    All the buzzt!
    CornedBee

    "There is not now, nor has there ever been, nor will there ever be, any programming language in which it is the least bit difficult to write bad code."
    - Flon's Law

  3. #18
    Registered User
    Join Date
    Feb 2004
    Posts
    31
    Quote Originally Posted by Cat
    If you are finding yourself needing to clean up using try..catch repeatedly, chances are you should be using containers for your pointers, which will automatically do the cleanup in their destructors.

    The beauty of exceptions is a single try...catch could deal with, say, thirty possible error conditions. You do need to write your code to not leak memory in the event of an exception, but it's not hard to make cleanup code happen in destructors (which means -- automatically).
    I guess you have a point. It does also mean, however, that you need to wrap every single C-style API you want to use instead of just going for plain error-checking.

    As mentioned earlier, I think the way to go is to use the best of both worlds really.
    Parts of my days are spent bug fixing...err. I’m sorry...I’ve just been reminded that we don’t have bugs. We have undocumented features. (Jonathan Ackley on Monkey Island 3)

  4. #19
    すまん Hikaru's Avatar
    Join Date
    Aug 2006
    Posts
    46
    Quote Originally Posted by CornedBee
    string, vector, and all the other containers with the default allocator throw an exception in the constructor if they can't allocate enough memory.
    Thank you.

  5. #20
    Registered User
    Join Date
    May 2003
    Posts
    1,619
    Quote Originally Posted by Halloko
    I guess you have a point. It does also mean, however, that you need to wrap every single C-style API you want to use instead of just going for plain error-checking.
    You don't need to do much, you just do:

    if (C_Style_Function() == ERROR_CONDITION)
    throw std::runtime_error("C_Style_Function failed!");

    The only things you wrap are things that require special cleanup. For example, you'd wrap a SOCKET because if you open it, you need to close it (i.e. special cleanup).
    You ever try a pink golf ball, Wally? Why, the wind shear on a pink ball alone can take the head clean off a 90 pound midget at 300 yards.

  6. #21
    Registered User
    Join Date
    Feb 2004
    Posts
    31
    Quote Originally Posted by Cat
    Here are examples of good ways of using try/catch:

    1. Say you are opening a file and then reading structures out of it, which you're putting into a std::map.

    You need ONE try...catch statement, which can deal with any of the following:
    * File can't be opened
    * File reading failed
    * Memory allocation failed
    * A duplicate structure ID already exists in the map
    etc., because in general, the overall program's response should be the same to any of these errors: give an error message (which you put in your exception), cleanup (which is done automatically as the exception propagates) and then do whatever you do when a file can't be opened (which is what happens in your catch)
    But what if you wanted to read multiple files and you wanted different things to happen based on the file you opened?

    Also, if you wanted to analyze the actual error you'd want to examine the exception a bit more, just as you would do in the standard return-value checking approach. Only difference is, the latter would be localized, that is, be located just next to the problematic piece of code instead of in a catch which could, potentially, be far away, codewise (depending on how big you keep your try/catches, of course).
    Parts of my days are spent bug fixing...err. I’m sorry...I’ve just been reminded that we don’t have bugs. We have undocumented features. (Jonathan Ackley on Monkey Island 3)

  7. #22
    Registered User
    Join Date
    Feb 2004
    Posts
    31
    Quote Originally Posted by Cat
    You don't need to do much, you just do:

    if (C_Style_Function() == ERROR_CONDITION)
    throw std::runtime_error("C_Style_Function failed!");
    But if you are checking the return values anyway, why not just handle the error locally instead of throwing an exception which is caught in a catch-statement further down?

    Am I missing the point here?
    Parts of my days are spent bug fixing...err. I’m sorry...I’ve just been reminded that we don’t have bugs. We have undocumented features. (Jonathan Ackley on Monkey Island 3)

  8. #23
    (?<!re)tired Mario F.'s Avatar
    Join Date
    May 2006
    Location
    Ireland
    Posts
    8,446
    Quote Originally Posted by Halloko
    but I've found that using c-style error checking rather than exceptions for most common cases such as file handling is the best approach.. for me.
    There's more to exceptions that just class constructors or memory allocation. They offer a mechanism that offers a more valid replacement of "traditional" methods.

    . Consider a function that returns an int and that implements error handling through negative values. Lets not even assume we would need those negative values for something else. Basically lets pretend the function only expects to return positive values and that negative values are error codes... For the sake of simplicity let's assume there's only one error code. -1.

    I'm hoping by now you are already imagining the mess your are getting yourself into. Everytime you call that function you have to check its return value and probably call another error handling function if the return value is -1. Everytime you don't, you are not checking for errors. Hopefully the error handling function will put the program back into a valid state. But what if -1 is a unrecoverable error code and the program needs to exit? Well, everytime you call that function, you cannot simply call a generic "exit" function. You will need to deallocate memory and do any other necessary cleaning. This will be different depending on where the function is called.

    But... let's still stick to the good ol' ways and pretend on this case the error was no severe. A simple error hadling function would solve the error. Nice, isn't it?... Well, what if after some time you just felt the need to add another error code. You just discovered another type of error that could occur. So now you have errors -1 and -2. So... back to code and change every piece of code that calls your function and checks for errors.

    And This was just about simple functions. I'm not going to enter into classes and polymorphism.

    Do consider c++ style exception handling proactively. There may be situations where it will not make sense. But before you reach that conclusion always look at them as your first choice for error handling routines.
    Originally Posted by brewbuck:
    Reimplementing a large system in another language to get a 25% performance boost is nonsense. It would be cheaper to just get a computer which is 25% faster.

  9. #24
    Registered User
    Join Date
    May 2003
    Posts
    1,619
    Quote Originally Posted by Halloko
    But what if you wanted to read multiple files and you wanted different things to happen based on the file you opened?
    If the difference was only in error message, use one try...catch.

    If the difference is more fundamental, either two try..catch (ideally, one around each file opening) or if that is impossible, throw two different exceptions, and have one try, two catch. Or restructure the code so that you could have two try...catch messages, e.g.

    Code:
      try{
        OpenFileA();
      }
      catch (std::runtime_error e){
        // Code for file A failing
      }
    
      try{
        OpenFileB();
      }
      catch (std::runtime_error e){
        // Code for file B failing
      }
    }
    Also, if you wanted to analyze the actual error you'd want to examine the exception a bit more, just as you would do in the standard return-value checking approach.
    Again, that typically involves just a different error message; if your error message is descriptive enough it usually is all you need.

    I'm sure there's some scenario where using standard C-style returns is far superior, but in 15 years of C++ that kind of thing hasn't happened much, if at all.
    You ever try a pink golf ball, Wally? Why, the wind shear on a pink ball alone can take the head clean off a 90 pound midget at 300 yards.

  10. #25
    Registered User
    Join Date
    Feb 2004
    Posts
    31
    Quote Originally Posted by Mario F.
    . Consider a function that returns an int and that implements error handling through negative values. Lets not even assume we would need those negative values for something else. Basically lets pretend the function only expects to return positive values and that negative values are error codes... For the sake of simplicity let's assume there's only one error code. -1.

    I'm hoping by now you are already imagining the mess your are getting yourself into. Everytime you call that function you have to check its return value and probably call another error handling function if the return value is -1. Everytime you don't, you are not checking for errors. Hopefully the error handling function will put the program back into a valid state. But what if -1 is a unrecoverable error code and the program needs to exit? Well, everytime you call that function, you cannot simply call a generic "exit" function. You will need to deallocate memory and do any other necessary cleaning. This will be different depending on where the function is called.

    But... let's still stick to the good ol' ways and pretend on this case the error was no severe. A simple error hadling function would solve the error. Nice, isn't it?... Well, what if after some time you just felt the need to add another error code. You just discovered another type of error that could occur. So now you have errors -1 and -2. So... back to code and change every piece of code that calls your function and checks for errors.
    You are making a very good point, Mario, and I will certainly consider using exceptions more in the future.[/qUOTE]

    HOWEVER, as you said yourself, the memory to deallocate and additional cleaning is different depending on where the function is called and the type of error. So, if you were using exceptions in your example above you would still have to go back to every single piece of code and add exception handling code accordingly.

    I agree, though, that it is somewhat easier to propagate an error back through your call stack when using exceptions rather than simple return values.
    Parts of my days are spent bug fixing...err. I’m sorry...I’ve just been reminded that we don’t have bugs. We have undocumented features. (Jonathan Ackley on Monkey Island 3)

  11. #26
    Registered User
    Join Date
    Feb 2004
    Posts
    31
    Quote Originally Posted by Cat
    Again, that typically involves just a different error message; if your error message is descriptive enough it usually is all you need.
    It's fun how you keep talking about error messages. Personally, I'm much more interested in the actual type of error, what caused it and how I can make the application continue. The error message is just a side effect of that for use in a log or to alert the user.

    Code:
     try{
        OpenFileA();
      }
      catch (std::runtime_error e){
        // Code for file A failing
      }
    
      try{
        OpenFileB();
      }
      catch (std::runtime_error e){
        // Code for file B failing
      }
    }
    I don't see the advantage of using exceptions in such a case rather than using ordinary return-value checking. In my opinion, they are two sides of the same coin (if that is an English idiom ) which does the exact same thing in almost the same way.
    Parts of my days are spent bug fixing...err. I’m sorry...I’ve just been reminded that we don’t have bugs. We have undocumented features. (Jonathan Ackley on Monkey Island 3)

  12. #27
    Registered User
    Join Date
    May 2003
    Posts
    1,619
    Quote Originally Posted by Halloko
    But if you are checking the return values anyway, why not just handle the error locally instead of throwing an exception which is caught in a catch-statement further down?

    Am I missing the point here?
    Because, in general, you're duplicating a whole lot of code. You typically would:
    1. Cleanup (actually not needed if you're coding smart and letting everything clean itself).
    2. Report the error to the user
    3. Make a choice to continue/abort the program

    In general, there will often be 10-20 different error checks that require the same prompt (maybe a different string displayed) and the same choice. In general, there are often a whole series of error checks which result in using the same exact code to recover. For example, any failure to open and read a file, whatever caused it, is probably recovered by informing the user, then using blank/default data, or possibly terminating the program if normal operation cannot continue without that file.

    Exceptions provide a way to do this fast, and also to force users to error check -- the program cannot continue if there
    is an error that isn't handled. If you forget to check a return code, the program continues with the error uncaught.

    Exceptions also make it easier to propagate an error up a chain of function calls (which is your other option to reduce the amount of code duplication). You only do the error-checking on the deepest call; the others just transparently propagate the error through. Without that, you need to do a check on each level.
    Last edited by Cat; 08-30-2006 at 07:33 PM.
    You ever try a pink golf ball, Wally? Why, the wind shear on a pink ball alone can take the head clean off a 90 pound midget at 300 yards.

  13. #28
    semi-colon generator ChaosEngine's Avatar
    Join Date
    Sep 2005
    Location
    Chch, NZ
    Posts
    597
    Quote Originally Posted by Cat
    Exceptions also make it easier to propagate an error up a chain of function calls (which is your other option to reduce the amount of code duplication). You only do the error-checking on the deepest call; the others just transparently propagate the error through. Without that, you need to do a check on each level.
    to add to that, exceptions allow you deal with the error at a more logically correct code location. let's say you have some sort of xmlserialisation system that converts objects to xml and back. as xml is nested this will typically involve recursive function calls. so you're 5 calls down the stack and you discover an xml element is mis-named. What do you do? your xmlserialisation doesn't know what the object is. In one instance it might be application critical, in another it might be trivial. exceptions allow you to write code like the following:

    Code:
    void initSystem()
    {
        try
        {
              deserialise(systemCriticalObj);
        }
        catch(xmlError &)
       {
           // uh-oh, we're stuffed, clean-up and exit
       }
    
       try
       {
            deserialise(guiSkin);
        }
        catch(xmlError &)
        {
           // meh, we'll just carry on with the default skin
        }
    }
    initSystem() knows the CONTEXT of the error and can make an intelligent decision about the error. deserialise() does not.
    "I saw a sign that said 'Drink Canada Dry', so I started"
    -- Brendan Behan

    Free Compiler: Visual C++ 2005 Express
    If you program in C++, you need Boost. You should also know how to use the Standard Library (STL). Want to make games? After reading this, I don't like WxWidgets anymore. Want to add some scripting to your App?

  14. #29
    Registered User VirtualAce's Avatar
    Join Date
    Aug 2001
    Posts
    9,607
    I rarely do allocation or anything to do with any resource in a constructor. I know it violates RAII to a point, but it's so messy trying to recover from an error in a constructor. It's either messy in your code using try catch blocks and or it's messy in it's operation w/o try catch blocks.

    Besides what are you going to do to recover from the error if the object being created is vital to program execution? Some errors you just cannot recover from.

    So I avoid it altogether. Constructors in most of my code simply init variables that are guaranteed NOT to fail. In other words setting pointers to NULL, numeric vars to initial values, etc, etc. This may not be the best approach, but it helps me out a great deal. The others here might have better solutions.


    However, I do all of my cleanup in the destructor unless I'm coding using MFC in which case this approach does not always bode well or coincide with the lifetime of GDI objects in all instances.

  15. #30
    (?<!re)tired Mario F.'s Avatar
    Join Date
    May 2006
    Location
    Ireland
    Posts
    8,446
    There's a quote I saw when browsing through my local bookstore. Can't remember the book, but it's one of those in my To Buy list.

    The quote goes something like this: There's two ways to deal with exceptions. Only the third is correct.

    Point being it's an hard subject and there is probably nothing much that can be said for exception handling that cannot be said against it. But the opposite is also true. And exception handling, with all the idiosyncrasies it may offer, is a necessary part of our programming experience. No one can pretend they have the answer to their correct usage. But that should never be a case against its usage. Exception handling offer a mechanism to report and handle exceptions. Deciding what is an exception is definitely the crux of the whole process. There's no right answer.
    Originally Posted by brewbuck:
    Reimplementing a large system in another language to get a 25% performance boost is nonsense. It would be cheaper to just get a computer which is 25% faster.

Popular pages Recent additions subscribe to a feed

Similar Threads

  1. Replies: 1
    Last Post: 06-10-2008, 08:38 PM
  2. C++ have a constructor call another constructor
    By QuestionC in forum C++ Programming
    Replies: 4
    Last Post: 05-17-2007, 01:59 AM
  3. Replies: 3
    Last Post: 03-26-2006, 12:59 AM
  4. Need help in classes
    By LBY in forum C++ Programming
    Replies: 11
    Last Post: 11-26-2004, 04:50 AM
  5. Constructor with Parameter not Firing
    By BillBoeBaggins in forum Windows Programming
    Replies: 4
    Last Post: 08-26-2004, 02:17 PM