C++ 错误处理,无需绕过堆栈展开过程

C++ Error Handling without bypassing stack unwinding process

提问人:stackoverfIow 提问时间:6/16/2023 更新时间:6/16/2023 访问量:52

问:

我希望在由许多源文件和头文件组成的代码中进行干净的错误处理。我更喜欢使用传统的 C 样式状态代码(调用函数,成功时返回 1,然后检查返回值是否为 1),如果状态代码不是 1,那么我想调用一个错误函数,如下所示。这样一来,我就不必在我的程序中到处发送垃圾邮件 try-catch,它允许我描述代码中可能发生的实际错误。我在下面的示例中遇到的问题是我需要清理内存(假设打开了数据库之类的东西),并且必须在程序退出之前关闭它以避免进一步的问题。为了实现这一点,我想利用 RAII,这意味着当堆栈展开过程开始时,应该调用类的析构函数。但是,很明显,在下面的示例中从未调用析构函数,因为 cout 不会发生。我知道这可以通过使用 try-catch 捕获所有错误来避免,否则堆栈倒带是“实现定义的”(哪个实现?这是指我的代码吗?但我真的想避免经常把所有东西都放在 try-catch 中,因为我的可读性会大大受到影响。

这是我的问题:是否可以只调整函数,以便在一切停止之前调用析构函数(我想做正常的堆栈展开)?如果不可能,为什么?这似乎是每个人都应该遇到的简单问题,所以我很惊讶没有发现 sth 会首先调用所有析构函数,而不是 throw。我知道这里有很多关于 RAII 的问题,但通读它们并没有帮助我解决我的问题。throw_error()safe_throw

感谢您的帮助。这是我的示例代码:

#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
}
C++ RAII

评论

0赞 Pepijn Kramer 6/16/2023
查找 std::expected 以及它背后的哲学。顺便说一句,try/catch 的整个想法是,如果你需要过度使用它,你不要在整个代码中发送垃圾邮件。例外情况应该是......异常。
0赞 john 6/16/2023
这里真的没有调用析构函数吗?在我看来应该是。
0赞 john 6/16/2023
无论如何,一个简单的尝试......main 中的 catch 确保调用析构函数。这是可以接受的吗?
0赞 stackoverfIow 6/16/2023
@john是的,在我提供的代码中没有调用析构函数。是的,main() 中的一个 try-catch 可以解决问题。我担心的是我会在具有许多源文件和头文件的大型项目中使用此结构,将所有内容包装到 main() 中的一个 try-catch 中真的可以吗?我曾经用 Python 编写代码,其中 try-catch 的使用被视为糟糕/懒惰的代码
0赞 john 6/16/2023
@stackoverfIow 如果它满足您的要求,那么我看不出问题所在,不确定 python 的最佳实践是否延续到 C++,因为语言相当不同。定义的实现是指编译器/平台。

答:

0赞 Wutz 6/16/2023 #1

你描述的异常的缺点让我觉得你真的不知道如何正确使用它们。在实践中,在使用异常来传达错误的程序中,您只需要很少的 try-catches - 仅当您想要从异常中恢复时。

谁在实现未捕获异常的行为取决于:编译器实现的问题。

您可以通过在主目录中放置一个 try-catch 并简单地记录 + 退出来解决您当前的问题。所有例外情况都将冒泡到此时,届时将发生堆栈展开。如果使用多个线程,则必须在启动的每个线程的入口点中执行此操作。

但是我的 2 美分:如果你这样做,你还不如取消错误代码,首先正确使用异常。现在,通过将错误代码转换为异常来混合这两种方法。

评论

1赞 stackoverfIow 6/16/2023
感谢您的反馈,您是对的,在 main 中使用 try-except 会导致我混合拙劣(错误代码和例外)方法。但是,现有的代码库(将被调用的函数)是带有 int 返回码的旧 C 样式,因此我认为这种混合方法对代码可读性的影响较小。在 main 中使用一个 try-catch 似乎是迄今为止最简单的解决方案。