草庐IT

c++ - 从 C `goto` 错误处理范例转换到 C++ 异常处理范例

coder 2023-05-30 原文

我是一名学习 C++ 的 C 程序员。在 C 中,有一个常见的 goto idiom used to handle errors and exit cleanly from a function .我读过通过 try-catch block 处理异常是面向对象程序中的首选,但我在 C++ 中实现这个范例时遇到了麻烦。

以 C 中的以下函数为例,它使用 goto 错误处理范例:

unsigned foobar(void){
    FILE *fp = fopen("blah.txt", "r");
    if(!fp){
        goto exit_fopen;
    }

    /* the blackbox function performs various
     * operations on, and otherwise modifies,
     * the state of external data structures */
    if(blackbox()){
        goto exit_blackbox;
    }

    const size_t NUM_DATUM = 42;
    unsigned long *data = malloc(NUM_DATUM*sizeof(*data));
    if(!data){
        goto exit_data;
    }

    for(size_t i = 0; i < NUM_DATUM; i++){
        char buffer[256] = "";
        if(!fgets(buffer, sizeof(buffer), fp)){
            goto exit_read;
        }

        data[i] = strtoul(buffer, NULL, 0);
    }

    for(size_t i = 0; i < NUM_DATUM/2; i++){
        printf("%lu\n", data[i] + data[i + NUM_DATUM/2]);
    }

    free(data)
    /* the undo_blackbox function reverts the
     * changes made by the blackbox function */
    undo_blackbox();
    fclose(fp);

    return 0;

exit_read:
    free(data);
exit_data:
    undo_blackbox();
exit_blackbox:
    fclose(fp);
exit_fopen:
    return 1;
}

我尝试使用异常处理范例在 C++ 中重新创建函数:

unsigned foobar(){
    ifstream fp ("blah.txt");
    if(!fp.is_open()){
        return 1;
    }

    try{
        // the blackbox function performs various
        // operations on, and otherwise modifies,
        // the state of external data structures
        blackbox();
    }catch(...){
        fp.close();
        return 1;
    }

    const size_t NUM_DATUM = 42;
    unsigned long *data;
    try{
        data = new unsigned long [NUM_DATUM];
    }catch(...){
        // the undo_blackbox function reverts the
        // changes made by the blackbox function
        undo_blackbox();
        fp.close();
        return 1;
    }

    for(size_t i = 0; i < NUM_DATUM; i++){
        string buffer;
        if(!getline(fp, buffer)){
            delete[] data;
            undo_blackbox();
            fp.close();
            return 1;
        }

        stringstream(buffer) >> data[i];
    }

    for(size_t i = 0; i < NUM_DATUM/2; i++){
        cout << data[i] + data[i + NUM_DATUM/2] << endl;
    }

    delete[] data;
    undo_blackbox();
    fp.close();

    return 0;
}

我觉得我的 C++ 版本没有正确实现异常处理范例;事实上,由于随着函数的增长,catch block 中积累了清理代码,因此 C++ 版本的可读性更差,更容易出错。

我已经读到,由于称为 RAII 的东西,catch block 中的所有这些清理代码在 C++ 中可能是不必要的,但我不熟悉这个概念。 我的实现是否正确,或者有更好的方法来处理错误并干净地退出 C++ 中的函数?

最佳答案

RAII的原理|是您使用类类型来管理使用后需要清理的任何资源;清理是由析构函数完成的。

这意味着您可以创建一个本地 RAII 管理器,该管理器会在超出范围时自动清理它所管理的任何内容,无论是由于正常的程序流程还是异常。 catch block 应该永远不需要只是为了清理;仅当您需要处理或报告异常时。

在您的情况下,您拥有三个资源:

  • 文件fpifstream 已经是 RAII 类型,所以只需删除对 fp.close() 的冗余调用,一切都很好。
  • 分配的内存data。如果它是一个小的固定大小(这样),则使用本地数组,如果需要动态分配,则使用 std::vector;然后摆脱 delete.
  • blackbox设置的状态。

您可以为“黑盒”恶意代码编写自己的 RAII 包装器:

struct blackbox_guard {
    // Set up the state on construction
    blackbox_guard()  {blackbox();}

    // Restore the state on destruction
    ~blackbox_guard() {undo_blackbox();}

    // Prevent copying per the Rule of Three
    blackbox_guard(blackbox_guard const &) = delete;
    void operator=(blackbox_guard) = delete;
};

现在您可以删除所有错误处理代码;我会通过异常(抛出或允许传播)而不是神奇的返回值来指示失败,给出:

void foobar(){
    ifstream fp ("blah.txt"); // No need to check now, the first read will fail if not open
    blackbox_guard bb;

    const size_t NUM_DATUM = 42;
    unsigned long data[NUM_DATUM];   // or vector<unsigned long> data(NUM_DATUM);

    for(size_t i = 0; i < NUM_DATUM; i++){
        string buffer;

        // You could avoid this check by setting the file to throw on error
        // fp.exceptions(ios::badbit); or something like that before the loop
        if(!getline(fp, buffer)){
             throw std::runtime_error("Failed to read"); // or whatever
        }

        stringstream(buffer) >> data[i]; // or data[i] = stoul(buffer);
    }

    for(size_t i = 0; i < NUM_DATUM/2; i++){
        cout << data[i] + data[i + NUM_DATUM/2] << endl;
    }
}

关于c++ - 从 C `goto` 错误处理范例转换到 C++ 异常处理范例,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/28631167/

有关c++ - 从 C `goto` 错误处理范例转换到 C++ 异常处理范例的更多相关文章

  1. ruby-on-rails - 在 Rails 中将文件大小字符串转换为等效千字节 - 2

    我的目标是转换表单输入,例如“100兆字节”或“1GB”,并将其转换为我可以存储在数据库中的文件大小(以千字节为单位)。目前,我有这个:defquota_convert@regex=/([0-9]+)(.*)s/@sizes=%w{kilobytemegabytegigabyte}m=self.quota.match(@regex)if@sizes.include?m[2]eval("self.quota=#{m[1]}.#{m[2]}")endend这有效,但前提是输入是倍数(“gigabytes”,而不是“gigabyte”)并且由于使用了eval看起来疯狂不安全。所以,功能正常,

  2. ruby - 使用 ruby​​ 将 HTML 转换为纯文本并维护结构/格式 - 2

    我想将html转换为纯文本。不过,我不想只删除标签,我想智能地保留尽可能多的格式。为插入换行符标签,检测段落并格式化它们等。输入非常简单,通常是格式良好的html(不是整个文档,只是一堆内容,通常没有anchor或图像)。我可以将几个正则表达式放在一起,让我达到80%,但我认为可能有一些现有的解决方案更智能。 最佳答案 首先,不要尝试为此使用正则表达式。很有可能你会想出一个脆弱/脆弱的解决方案,它会随着HTML的变化而崩溃,或者很难管理和维护。您可以使用Nokogiri快速解析HTML并提取文本:require'nokogiri'h

  3. ruby-on-rails - Rails 常用字符串(用于通知和错误信息等) - 2

    大约一年前,我决定确保每个包含非唯一文本的Flash通知都将从模块中的方法中获取文本。我这样做的最初原因是为了避免一遍又一遍地输入相同的字符串。如果我想更改措辞,我可以在一个地方轻松完成,而且一遍又一遍地重复同一件事而出现拼写错误的可能性也会降低。我最终得到的是这样的:moduleMessagesdefformat_error_messages(errors)errors.map{|attribute,message|"Error:#{attribute.to_s.titleize}#{message}."}enddeferror_message_could_not_find(obje

  4. ruby - 如何指定 Rack 处理程序 - 2

    Rackup通过Rack的默认处理程序成功运行任何Rack应用程序。例如:classRackAppdefcall(environment)['200',{'Content-Type'=>'text/html'},["Helloworld"]]endendrunRackApp.new但是当最后一行更改为使用Rack的内置CGI处理程序时,rackup给出“NoMethodErrorat/undefinedmethod`call'fornil:NilClass”:Rack::Handler::CGI.runRackApp.newRack的其他内置处理程序也提出了同样的反对意见。例如Rack

  5. ruby - 将数组的内容转换为 int - 2

    我需要读入一个包含数字列表的文件。此代码读取文件并将其放入二维数组中。现在我需要获取数组中所有数字的平均值,但我需要将数组的内容更改为int。有什么想法可以将to_i方法放在哪里吗?ClassTerraindefinitializefile_name@input=IO.readlines(file_name)#readinfile@size=@input[0].to_i@land=[@size]x=1whilex 最佳答案 只需将数组映射为整数:@land边注如果你想得到一条线的平均值,你可以这样做:values=@input[x]

  6. ruby - 将散列转换为嵌套散列 - 2

    这道题是thisquestion的逆题.给定一个散列,每个键都有一个数组,例如{[:a,:b,:c]=>1,[:a,:b,:d]=>2,[:a,:e]=>3,[:f]=>4,}将其转换为嵌套哈希的最佳方法是什么{:a=>{:b=>{:c=>1,:d=>2},:e=>3,},:f=>4,} 最佳答案 这是一个迭代的解决方案,递归的解决方案留给读者作为练习:defconvert(h={})ret={}h.eachdo|k,v|node=retk[0..-2].each{|x|node[x]||={};node=node[x]}node[

  7. ruby-on-rails - 如何优雅地重启 thin + nginx? - 2

    我的瘦服务器配置了nginx,我的ROR应用程序正在它们上运行。在我发布代码更新时运行thinrestart会给我的应用程序带来一些停机时间。我试图弄清楚如何优雅地重启正在运行的Thin实例,但找不到好的解决方案。有没有人能做到这一点? 最佳答案 #Restartjustthethinserverdescribedbythatconfigsudothin-C/etc/thin/mysite.ymlrestartNginx将继续运行并代理请求。如果您将Nginx设置为使用多个上游服务器,例如server{listen80;server

  8. ruby-on-rails - 迷你测试错误 : "NameError: uninitialized constant" - 2

    我遵循MichaelHartl的“RubyonRails教程:学习Web开发”,并创建了检查用户名和电子邮件长度有效性的测试(名称最多50个字符,电子邮件最多255个字符)。test/helpers/application_helper_test.rb的内容是:require'test_helper'classApplicationHelperTest在运行bundleexecraketest时,所有测试都通过了,但我看到以下消息在最后被标记为错误:ERROR["test_full_title_helper",ApplicationHelperTest,1.820016791]test

  9. ruby-on-rails - Rails - 乐观锁定总是触发 StaleObjectError 异常 - 2

    我正在学习Rails,并阅读了关于乐观锁的内容。我已将类型为integer的lock_version列添加到我的articles表中。但现在每当我第一次尝试更新记录时,我都会收到StaleObjectError异常。这是我的迁移:classAddLockVersionToArticle当我尝试通过Rails控制台更新文章时:article=Article.first=>#我这样做:article.title="newtitle"article.save我明白了:(0.3ms)begintransaction(0.3ms)UPDATE"articles"SET"title"='dwdwd

  10. ruby-on-rails - 如何在 Rails View 上显示错误消息? - 2

    我是rails的新手,想在form字段上应用验证。myviewsnew.html.erb.....模拟.rbclassSimulation{:in=>1..25,:message=>'Therowmustbebetween1and25'}end模拟Controller.rbclassSimulationsController我想检查模型类中row字段的整数范围,如果不在范围内则返回错误信息。我可以检查上面代码的范围,但无法返回错误消息提前致谢 最佳答案 关键是您使用的是模型表单,一种显示ActiveRecord模型实例属性的表单。c

随机推荐