如何在错误处理中避免goto

时间:2015-03-24 10:42:13

标签: c++ error-handling goto

我知道用goto来阅读程序是多么可怕和困难,而且我想像其他人一样不惜一切代价避免使用它们。

然而,我遇到了一个情况,我有一个很难避免他们同时拥有一个优雅的程序。这是错误处理。我做了很多步骤,如果在任何步骤中出现错误,程序应显示错误消息,释放一些内存并返回false。如果一切正常,则应返回true

原始程序是这样的:

bool init_routine()
{
     int errcode;   // Error code
     errcode = do_someting();
     if(errcode != RETURN_SUCCESS)
     {
         free_some_memory();
         std::cerr << "Something went wrong in initialization.";
         return false;
     }
     errcode = do_something_else();
     if(errcode != RETURN_SUCCESS)
     {
         free_some_more_memory();   // Was allocated in the do_something function
         free_some_memory();
         std::cerr << "Something went wrong in initialization.";
         return false;
     }
     /* The same pattern repeats quite a few times... */
     return true;   // Everything was ok
}

这很难看,因为有些代码会反复粘贴,这是不好的做法。我可以使用一个函数,但是函数不能来自被调用者return false,因为C ++缺少我所谓的双返回指令

另一种方法是使用嵌套的if

bool init_routine()
{
     int errcode;   // Error code
     errcode = do_someting();
     if(errcode == RETURN_SUCCESS)
     {
         errcode = do_something_else();
         if(errcode == RETURN_SUCCESS)
         {
             /* The same pattern repeats quite a few times */
             if()
             {
                if()
                {
                      if()
                      {
                         return true;
                      }
                }
             }
         }
         free_some_more_memory();
     }
     free_some_memory();
     std::cerr << "Something went wrong in initialization.";
     return false;
}

这避免了任何重复并且紧凑。但是,随着嵌套if的数量快速增长,缩进会受到阻碍。此外,所有if隐藏了程序的简单控制流程,它只是顺序执行指令并退出错误,并使读者相信有许多复杂的条件要遵循。

另一种方式,在我看来更自然,是:

bool init_routine()
{
     int errcode;   // Error code
     errcode = do_someting();
     if(errcode != RETURN_SUCCESS)
     {
         goto err1;
     }
     errcode = do_something_else();
     if(errcode != RETURN_SUCCESS)
     {
         goto err2;
     }
     /* The same pattern repeats quite a few times... */
     return true;   // Everything was ok

err2:
     free_some_more_memory();
err1:
     free_some_memory();
     std::cerr << "Something went wrong in initialization";
     return false;
}

没有重复,唯一的问题是使用邪恶的goto。我的问题是:

有没有办法不使用goto,而不是使用重复,不使用例外,而不是疯狂的缩进?

编辑:为了说清楚,我不能使用异常,因为我调用的例程来自用C编写的外部库,我用C ++代码调用它们。很抱歉没有提及。

6 个答案:

答案 0 :(得分:2)

如果您必须明确分配,请使用标准集合或智能指针,例如std::unique_ptr。他们自动进行解除分配。那就是它。

更一般地说,使用带有析构函数的对象进行清理(标准集合和智能指针的作用)。这被称为RAII,资源获取是初始化。 Google要了解更多内容,并在教科书的索引中查找。

顺便说一句,将输出混合到一般功能中并不是一个好主意。如果要在GUI程序中使用该代码,该怎么办?

答案 1 :(得分:1)

如果您使用代表所有权的对象管理已分配的资源(例如使用智能指针或类似的东西),那么这些问题就不会发生:

resource r;
if (int e = alloc_resource(&r))
    return e;

r的析构函数将负责在范围退出时释放资源。

使用错误代码而不是异常是完全愚蠢的,因为它们占用了返回值点,只携带有限的大量信息,你可以忘记检查它们。使用例外。

答案 2 :(得分:1)

不问为什么你试图避免例外:

有几种方法可以进行错误处理。首先是C风格的方式。返回表示失败的integerbool(而不是void)或返回integer上的错误代码(在C中,通常全局变量errno是用于此)。丑陋的goto

更多C ++ - 风格方式:

如果您的函数返回对象或值,则可以定义无效的对象(例如带有width = height = 0的图像)。

你提到的c ++缺少“双返回”在c ++ 11中不存在。你可以这样做:return std::make_pair(obj,true)

如果您需要在错误情况下处理资源,我会给您以下建议:不要这样做。请改用 RAII 。它可以节省你很多efford并且可以节省。

答案 3 :(得分:1)

goto通常被禁止,因为这通常导致没有结构的if-then-goto语句的鼠窝。循环和函数调用是更好的方法。

已经说过,当涉及到深度嵌套循环或错误处理goto时,这是一种适当的处理方式。 goto清理模式在linux内核中广泛用于错误处理。所以对于C风格的编程,你所做的事情是完全可以接受的。

然而,在C ++中,通常有更好的选择。 RAII和智能指针是一个很好的一等奖。异常应该涵盖goto的其他需求,尽管嵌入式系统通常会禁用异常。

答案 4 :(得分:0)

标准方法是使用unique_ptr或类似方法。如果这对您来说太不方便,并且您使用的是c ++ 0x或更高版本,则可以使用finally lambdas:

T* resource = some_legacy_c_function();
if(!resource) return false; //or throw, or whatever
FINALLY([&](){
    cleanup(resource);
});
do_stuff_with(resource);
return;//cleanup is called automatically here :)

将finally宏定义如下(将其放在标题中的某处):

template<typename F>
class _Finally
{
private:
    F _f;

public:
    inline _Finally(const F& f) :
        _f(f)
    {
    }

    inline ~_Finally(){
        _f();
    };
};

template<typename F>
inline _Finally<F> Finally(const F& f)
{
    return _Finally<F>(f);
}

#define FINALLY auto __finally_##__LINE__ = Finally

答案 5 :(得分:0)

  1. 虽然我不使用goto,但在这种情况下,有一些受尊敬的程序员会使用forward-gotos。无论如何,还有另一种解决方案:创建一次性循环。
  2. 我也同意其他人,如果可能的话使用RAII。但是在某些情况下(例如遗留的 do_something()),你可以使用布尔标志做一些'manual-RAII'。
  3. 所以你的代码可以像这样重构:

    bool init_routine()
    {
        int errcode;   // Error code
        bool need_free_some_memory = false;
        bool need_free_some_more_memory = false;
    
        do { //not a loop, just scope for break
    
            need_free_some_memory = true;
            errcode = do_something();
    
            if(errcode != RETURN_SUCCESS)
                break;
    
            need_free_some_more_memory = true;
            errcode = do_something_else();
            if(errcode != RETURN_SUCCESS)
                break;
    
        } while(0);
    
        if(need_free_some_more_memory)
            free_some_more_memory();   // Was allocated in the do_something function
        if(need_free_some_memory)
            free_some_memory();
    
        if(errcode != RETURN_SUCCESS)
            std::cerr << "Something went wrong in initialization.";
    
        return errcode == RETURN_SUCCESS;   // Everything was ok
    }