Home > Software design >  C nested try-catch catching the same exceptions - how should I rewrite this code?
C nested try-catch catching the same exceptions - how should I rewrite this code?

Time:10-19

I currently have code in C 14 that looks like this:

try {
// create objects, process input
     myObject obj;  // may throw std::invalid_argument in constructor
    
    for (i = 0; i < n_files; i  )
    {
        try {
             process_file(); // may throw std::invalid_argument and std::runtime_error()
         }
         catch (std::exception& e)
          {// print error
              continue;
          }
    }

catch (std::exception& e)
 {// print error
     return 0;
 }

For my own functions, I'm just throwing std exceptions like std::runtime_error and std__invalid_exception.

The idea is that, objects created in the outer try block should throw exceptions and be caught by the outer catch block, which would then end the program. Exceptions thrown by process_file() would be caught by the inner try block, which would just print some error but not result in program termination, and the program would continue onto processing the next file.

The outer try block contains object constructor calls that would then be used in inner try-catch, so I can't simply move it to its own try-catch otherwise the objects would be undefined in the loop.

But this code as is won't work from what I understand, because exceptions thrown in the outer try block would hit the inner catch statement first, as that is the first catch statement reachable in the code. Also, nested try-catch like this would be bad form/confusing to read from what I've read.

What would be the proper way of doing this instead? Thanks!

CodePudding user response:

this code as is won't work from what I understand

Yes, it will work just fine, for the scenario you have described.

exceptions thrown in the outer try block would hit the inner catch statement first, as that is the first catch statement reachable in the code.

That is not correct. It is not about the order in code, but the order in scope. The inner try is not in scope if the outer try throws. The exception goes up the call stack, starting at the current scope, then its nearest outer scope, then the next outer scope, and so on, until it finds a matching catch. For example:

try {
    // if any std exception is thrown here, jumps to A below...

    try {
        // if std::invalid_argument is thrown here, jumps to B below...
        // if any other std exception is thrown here, jumps to A below...

        for (int i = 0; i < n_files;   i)
        {
            try {
                // if any std exception is thrown here, jumps to C below...
            }
            catch (std::exception& e) // C
            {
                // print error
                continue;
            }
        }

        // if std::invalid_argument is thrown here, jumps to B below...
        // if any other std exception is thrown here, jumps to A below...
    }
    catch (invalid_argument& e) // B
    {
        // print error
        return 0;
    }
}
catch (exception& e) // A
{
    // print error
    return 0;
}

nested try-catch like this would be bad form/confusing to read from what I've read.

That is also not correct. There is nothing wrong with using nested try blocks.

However, in this example, it would make more sense to have the inner try catch ONLY std::invalid_argument and std::runtime_error specifically, since those are the 2 types it is expecting and willing to ignore to continue the loop. Don't catch std::exception generally at that spot. That way, if process_file() throws something unexpected (say std::bad_alloc, for instance), then the outer catch should handle it to terminate the process.

try {
    // if any std exception is thrown here, jumps to A below...

    try {
        // if std::invalid_argument is thrown here, jumps to B below...
        // if any other std exception is thrown here, jumps to A below...

        for (int i = 0; i < n_files;   i)
        {
            try {
                // if std::invalid_argument is thrown here, jumps to D below...
                // if std::runtime_error is thrown here, jumps to C below...
                // if any other std exception is thrown here, jumps to A below...
            }
            catch (std::invalid_argument& e) // D
            {
                // print error
                continue;
            }
            catch (std::runtime_error& e) // C
            {
                // print error
                continue;
            }
        }

        // if std::invalid_argument is thrown here, jumps to B below...
        // if any other std exception is thrown here, jumps to A below...
    }
    catch (invalid_argument& e) // B
    {
        // print error
        return 0;
    }
}
catch (exception& e) // A
{
    // print error
    return 0;
}

The best way to design a catch is to have it catch only the specific types of exceptions it knows how to handle at that spot in the code. Let an outer catch handle everything else. If an exception is thrown and no matching catch is found to handle it, the process will terminate by default.

  • Related