当代码流是这样的:
if(check())
{
...
...
if(check())
{
...
...
if(check())
{
...
...
}
}
}
我通常看到这种方法可以避免上述混乱的代码流:
do {
if(!check()) break;
...
...
if(!check()) break;
...
...
if(!check()) break;
...
...
} while(0);
有什么更好的方法可以避免这种工作/黑客,从而使其成为更高级别(行业级别)的代码?
任何跳出盒子的建议都是欢迎的!
有些时候,使用goto实际上是正确的答案——至少对那些没有在宗教信仰中长大的人来说,“不管问题是什么,goto永远都不是答案”——这是其中的一个例子。
此代码使用了do{…}, (0);唯一的目的就是把一个傻瓜打扮成休息的样子。如果你打算使用goto,那就公开使用它。让代码更难阅读是没有意义的。
一种特殊的情况是当你有很多代码和相当复杂的条件时:
void func()
{
setup of lots of stuff
...
if (condition)
{
...
...
if (!other condition)
{
...
if (another condition)
{
...
if (yet another condition)
{
...
if (...)
...
}
}
}
....
}
finish up.
}
没有如此复杂的逻辑,实际上可以更清楚地表明代码是正确的。
void func()
{
setup of lots of stuff
...
if (!condition)
{
goto finish;
}
...
...
if (other condition)
{
goto finish;
}
...
if (!another condition)
{
goto finish;
}
...
if (!yet another condition)
{
goto finish;
}
...
....
if (...)
... // No need to use goto here.
finish:
finish up.
}
编辑:澄清一下,我绝不是建议使用goto作为通用解决方案。但在某些情况下,goto是比其他解决方案更好的解决方案。
例如,想象一下,我们正在收集一些数据,测试的不同条件是某种“这是正在收集的数据的结束”——这取决于某种“继续/结束”标记,这些标记根据您在数据流中的位置而变化。
现在,当我们完成之后,我们需要将数据保存到一个文件中。
是的,通常有其他解决方案可以提供合理的解决方案,但并不总是如此。
我对这里提出的不同答案的数量感到惊讶。但是,最后在我必须更改的代码中(即删除这个do-while(0)黑客或任何东西),我做了一些与这里提到的任何答案不同的事情,我很困惑为什么没有人想到这一点。以下是我所做的:
初始代码:
do {
if(!check()) break;
...
...
if(!check()) break;
...
...
if(!check()) break;
...
...
} while(0);
finishingUpStuff.
Now:
finish(params)
{
...
...
}
if(!check()){
finish(params);
return;
}
...
...
if(!check()){
finish(params);
return;
}
...
...
if(!check()){
finish(params);
return;
}
...
...
所以,这里所做的是,整理的东西被隔离在一个函数中,事情突然变得如此简单和干净!
我认为这个解决方案值得一提,所以在这里提供了它。
首先,一个简短的例子来说明为什么goto不是一个好的c++解决方案:
struct Bar {
Bar();
};
extern bool check();
void foo()
{
if (!check())
goto out;
Bar x;
out:
}
试着把它编译成一个object文件,看看会发生什么。然后尝试等价的do+ break + while(0)。
这是题外话。主要观点如下。
如果整个函数失败,这些小块的代码通常需要某种类型的清理。当您“展开”部分完成的计算时,这些清理通常希望以与块本身相反的顺序发生。
获得这些语义的一个选项是RAII;参见@utnapistim的回答。c++保证自动析构函数以与构造函数相反的顺序运行,这自然提供了一个“unwind”。
但这需要大量的RAII类。有时一个更简单的选择是使用堆栈:
bool calc1()
{
if (!check())
return false;
// ... Do stuff1 here ...
if (!calc2()) {
// ... Undo stuff1 here ...
return false;
}
return true;
}
bool calc2()
{
if (!check())
return false;
// ... Do stuff2 here ...
if (!calc3()) {
// ... Undo stuff2 here ...
return false;
}
return true;
}
...等等。这很容易审计,因为它把“撤销”代码放在“做”代码旁边。简单的审计是好的。它还使控制流非常清晰。对于C来说,这也是一个有用的模式。
它可能要求calc函数接受大量参数,但如果您的类/结构具有良好的内聚性,这通常不是问题。(也就是说,属于一起的东西生活在一个对象中,所以这些函数可以作为指向少量对象的指针或引用,但仍然可以做很多有用的工作。)