c++raii

C++ Error Handling without bypassing stack unwinding process


I want to have clean error handling in my code that consists of many source and header files. What I would prefer is using traditional C-style status codes (call function which returns 1 on success, then check whether returned value was 1) and if the status code is not 1 then I want to call an error function like you can see below. This way I avoid having to spam try-catch all over my program and it allows me to describe the actual error in the code where it might happen. The problem that I have in the example below is that I need to clean up memory (assume something like a database is opened) and it must be closed before the program exits to avoid further problems. To achieve this I want to make use of RAII which means that the destructor of the class is supposed to be called when the stack unwinding process begins. However, it is clear that the destructor never is called in the example below because the cout does not happen. I understand that this can be avoided by using try-catch to catch all errors otherwise the stack rewind is "implementation-defined" (Which implementation? Does this refer to MY code?). But I really want to avoid constantly having to put everything into try-catch because my could readability would greatly suffer.

Here is my question: Is it possible to adjust ONLY the function throw_error() so that the destructor is called before everything stops (I want to do the normal stack unwind)? If it is not possible, why? This seems like such a simple problem everyone should have encountered so I am surprised to not have found sth like safe_throw which instead of throw will first call all destructors. I know that there are many questions on here concerning RAII but reading through them did not help me solve my problem.

Thanks for your help. Here is my example code:

#include <iostream>

using namespace std;


void throw_error(const string& error_description){
    throw runtime_error(error_description); // i would like to have a different function that does the same thing except that the stack unwind happens
    
}


class DatabaseExample{
public: 

    // constructor
    DatabaseExample(){
        testarray = new int[32]; // can lead to memory leak if not deleted manually (class is called DatabaseExample because I plan to open a database connection and the leak would be not closing the database connection properly, this is just a simplified example)
    }
    
    // destructor, which sadly is never called because exit() or throw avoids the stack unwind which would call the destructor :(
    ~DatabaseExample(){
        cout << "Destructor of the class has been called.. Or has it?" << endl;
        delete[] testarray;
    }
    
private:
    int* testarray;
};


int main(){
    DatabaseExample testobject = DatabaseExample();
    
    int a = 2;  // simplified example
    if (a != 1){    // openssl library returns 1 on success
        throw_error("My custom error that occured because...");
    }
    

    return 0;   // imagine having 0 chosen as a status value convention from a successful program when from a boolean perspective 0 means FALSE.. Everything worked as expected and the program succeeded in everything it was supposed to do and yet it is supposed to return false
}

Solution

  • The drawbacks of exceptions that you describe make me think that you don't really know how to use them correctly. In practice, in a program that uses exceptions to communicate errors, you'll need few try-catches - only when you want to recover from one.

    The question whos implementation the behavior of uncaught exceptions depends on: your compilers implementation.

    You can solve your current issue by putting one try-catch in the main and simply log + exit. All exceptions will bubble up to this point, where stack unwinding will occur. If you work with multiple threads you'll have to do this in the entrypoint of every thread you start.

    But my 2 cents: if you do that, you might as well do away with the error codes and just use exceptions properly in the first place. Right now you're mixing both approaches by converting error codes to exceptions.