Search code examples
c++error-handlingexceptioncoding-stylecode-cleanup

C++ exception handling confusion


So, basically I have this simple wrapper code for an external C library and I'm a newbie in terms of proper exception handling.

In advance: The code shows the same problem two times, but maybe there's a different solution for the class version.

#include <some_c_lib>

void setup(){
    //some setup code

    //init function from the C library
    //with C-style return code for error handling
    if(!init()){ 
        //error: program should terminate
        //because error cannot be handled
    }

    //some setup code
}

class myclass{
    //some members

public:
    myclass(){
        //some construction code

        //create function of the C library
        //with C-style return code error handling
        if(!create()){
            //error: program should terminate
            //because error cannot be handled
        }
    }

    ~myclass(){
        //desturction code
    }
};

int main(){
    std::ostream log("log.txt");  //logfile as an example

    setup();

    myclass obj;

    while(everything_is_fine()){
        //main loop stuff
    }

}

The problem is: I don't know what's the best way to terminate the program. I don't want to catch an exception in main. It would be kind of pointless and ugly, because the exception cannot be handled anyway. Even so, I'd like to have some sort of stack unwinding mechanism. If I simply exit the program inside the if blocks, then a log file for example wouldn't be destroyed properly. Am I right?

  • Would the file close if I throw inside the if but without providing a try-catch block anywhere?

  • How to deal with exceptions occuring in constructors?

  • Is there a best way to handle this type of problem?

I hope it became clear what my problem is.

Thanks for your answers, Have a nice day or evening.


Solution

  • It depends, and you really haven't given enough information. Generally speaking, there is no absolute "best" - it depends on needs of your program, rather than there being a "one size fits all" approach.

    It is only true in small trivial programs (e.g. the sort of things you'll do in class exercises, rather than a workplace) that an error always requires immediate program termination. The real-world need is fuzzier than that - depending on what your program does, there is often an option to recover from the error and continue (either normally, or in some degraded mode). It may also be preferable to take steps to prevent an error (e.g. detect bad data, and do something to recover before doing an operation on bad data and causing an error condition).

    Generally, though, if an error occurs in a constructor (and it is unavoidable, and the constructor can't do anything to recover once it occurs, etc) it is necessary to throw an exception. This basically signals a need for the caller (or some function in the call stack) to take recovery action. If the caller is unable to recover, the default result of throwing an exception is program termination - after invoking destructors of all objects created locally (of automatic storage duration) in the call stack. This terminates the program - if that is necessary - and does cleanup in the process, as long as destructors clean up properly (which is the purpose of destructors).

    In your code, throwing an exception will (eventually) return control to main(). If the exception is not caught, the program will terminate - but not before log is destroyed - which invokes its destructor. The destructor of standard output stream classes generally flush the stream and close it properly. If you need to do more than that (e.g. other recovery action before terminating, after flushing the stream) write main() as a function-try-block.

    It is usually inadvisable to do "partial construction" in a constructor - for example, a constructor setting up some basics, but the user then having to call another function to do "further" initialisation. Such techniques are an opportunity to forget to do the initialisation - which basically means subsequent code gets to use an object that is not initialised properly. In C++, such techniques are rarely necessary anyway - it is possible to hold off on creating an object until all information is available to properly initialise it (in the constructor).

    Generally speaking, returning error codes (e.g. a function with a non-void return type, a function that accepts a pointer/reference to an object which stores status information) is appropriate in different circumstances. There is nothing that forces a caller to check the return value from a function. So a return code is appropriate if an error condition can be safely ignored (e.g. if your code forgets to check it) or if the function is only used in a circumstance where the return code will be checked. There is nothing preventing you writing code that converts a return code (say, from a function written in C) into an exception. The problem with return codes is that it is possible to forget to check them - which can mean critical errors remain undetected/unreported, and cause faults in other code within the program.