【问题标题】:C++ exception handling confusionC++ 异常处理混乱
【发布时间】:2016-11-18 20:42:29
【问题描述】:

所以,基本上我有这个用于外部 C 库的简单包装器代码,而且在正确处理异常方面我是新手。

提前:代码两次显示相同的问题,但对于类版本可能有不同的解决方案。

#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
    }

}

问题是:我不知道终止程序的最佳方法是什么。 我不想在main 中发现异常。这将是一种毫无意义和丑陋的,因为无论如何都无法处理异常。即便如此,我还是想要某种堆栈展开机制。如果我只是 exit if 块内的程序,那么例如日志文件将不会被正确销毁。我对吗?

  • 如果我将文件放入 if 中但没有在任何地方提供 try-catch 块,文件会关闭吗?

  • 构造函数出现异常如何处理?

  • 是否有处理此类问题的最佳方法?

我希望我的问题很清楚。

感谢您的回答,祝您有美好的一天或晚上。

【问题讨论】:

    标签: c++ error-handling exception-handling coding-style code-cleanup


    【解决方案1】:

    视情况而定,而且您确实没有提供足够的信息。一般来说,没有绝对的“最佳”——这取决于您的程序的需要,而不是“一刀切”的方法。

    只有在小的琐碎程序(例如,您将在课堂练习中而不是在工作场所做的事情)中,错误总是需要立即终止程序才是正确的。现实世界的需求比这更模糊 - 根据您的程序所做的事情,通常有一个选项可以从错误中恢复并继续(正常或在某些降级模式下)。还最好采取措施防止错误(例如,检测坏数据,并在对坏数据执行操作并导致错误条件之前进行一些恢复)。

    不过,一般来说,如果构造函数中发生错误(这是不可避免的,并且构造函数一旦发生就无法进行任何恢复等),则有必要抛出异常。这基本上表明调用者(或调用堆栈中的某些函数)需要采取恢复操作。如果调用者无法恢复,则抛出异常的默认结果是程序终止 - 在调用堆栈中本地创建的所有对象(自动存储持续时间)的析构函数之后。只要析构函数正确清理(这是析构函数的目的),这将终止程序(如果有必要)并在过程中进行清理。

    在您的代码中,抛出异常将(最终)将控制权返回给main()。如果没有捕获到异常,程序将终止——但不会在log 被销毁之前终止——这会调用它的析构函数。标准输出流类的析构函数通常刷新流并正确关闭它。如果您需要做更多的事情(例如,在终止之前、刷新流之后的其他恢复操作)将main() 写为函数尝试块。

    通常不建议在构造函数中进行“部分构造” - 例如,构造函数设置了一些基础知识,但用户必须调用另一个函数来进行“进一步”初始化。这种技术是一个忘记进行初始化的机会——这基本上意味着后续代码会使用未正确初始化的对象。在 C++ 中,无论如何都很少需要这种技术 - 可以推迟创建对象,直到所有信息都可用于正确初始化它(在构造函数中)。

    一般来说,返回错误代码(例如,具有非void 返回类型的函数,接受指向存储状态信息的对象的指针/引用的函数)适用于不同的情况。没有什么可以强制调用者检查函数的返回值。因此,如果可以安全地忽略错误条件(例如,如果您的代码忘记检查它)或者如果该函数仅在将检查返回码的情况下使用,则返回码是合适的。没有什么可以阻止您编写将返回码(例如,从用 C 编写的函数)转换为异常的代码。返回码的问题在于可能会忘记检查它们 - 这可能意味着关键错误仍未检测到/未报告,并导致程序中的其他代码出现错误。

    【讨论】:

    • 感谢您的回答,这真的很有帮助。就我的程序而言,确实没有办法从错误中恢复,因为错误来自我使用的 C 库,并且可能是由不兼容的硬件或其他一些外部因素引起的。目前该程序很小且微不足道,但我想将其设计为大型项目的基础。所以我认为从一开始就正确处理错误非常重要。我当然明白对于各种问题没有最好的解决方案,但我想了解如何处理错误。
    • 那么我说得对吗,在构造函数中抛出一个错误而不捕获它就足以强制程序终止?我发现无法验证使用此方法是否已正确清理所有内容。我试图从测试析构函数内部打印一些东西到控制台,但没有显示任何内容。你现在有一个方法我可以检查它吗?
    • 如果没有匹配的catch子句(包括catch (...),则调用terminate()terminate()的默认动作是调用abort()——即退出程序而不调用调用时存在的任何对象的析构函数。通常最好在main() 中捕获异常,然后正常终止,以确保正确清理。但是,可以注册终止处理程序。
    【解决方案2】:

    一种解决方案是不要以会引发错误的方式编写构造函数。一个常见的做法是使用构造函数来设置成员变量等,然后有一个方法如 bool initialize();它根据类是否可以进行更复杂的初始化而没有错误返回一个值。

    您也可以返回其他值或结构,而不是 bool,以获取更多信息错误。

    最后,您的日志文件仍然可以被任何类写入,并且应该包含有关错误的信息(如果有的话)。

    【讨论】:

    • 感谢您的回答。它并不能真正帮助我解决我的问题,但无论如何它对于未来可能发生的问题都是有用的信息。所以我给了你一个赞成票来补偿你的反对票:)
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-01-20
    • 1970-01-01
    • 2012-06-11
    • 2011-04-27
    • 2011-09-11
    • 1970-01-01
    相关资源
    最近更新 更多