我知道用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
,而不是使用重复,不使用例外,而不是疯狂的缩进?
答案 0 :(得分:2)
如果您必须明确分配,请使用标准集合或智能指针,例如std::unique_ptr
。他们自动进行解除分配。那就是它。
更一般地说,使用带有析构函数的对象进行清理(标准集合和智能指针的作用)。这被称为RAII,资源获取是初始化。 Google要了解更多内容,并在教科书的索引中查找。
顺便说一句,将输出混合到一般功能中并不是一个好主意。如果要在GUI程序中使用该代码,该怎么办?
答案 1 :(得分:1)
如果您使用代表所有权的对象管理已分配的资源(例如使用智能指针或类似的东西),那么这些问题就不会发生:
resource r;
if (int e = alloc_resource(&r))
return e;
r
的析构函数将负责在范围退出时释放资源。
使用错误代码而不是异常是完全愚蠢的,因为它们占用了返回值点,只携带有限的大量信息,你可以忘记检查它们。使用例外。
答案 2 :(得分:1)
不问为什么你试图避免例外:
有几种方法可以进行错误处理。首先是C风格的方式。返回表示失败的integer
或bool
(而不是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)
goto
,但在这种情况下,有一些受尊敬的程序员会使用forward-gotos
。无论如何,还有另一种解决方案:创建一次性循环。所以你的代码可以像这样重构:
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
}