当代码流是这样的:

if(check())
{
  ...
  ...
  if(check())
  {
    ...
    ...
    if(check())
    {
      ...
      ...
    }
  }
}

我通常看到这种方法可以避免上述混乱的代码流:

do {
    if(!check()) break;
    ...
    ...
    if(!check()) break;
    ...
    ...
    if(!check()) break;
    ...
    ...
} while(0);

有什么更好的方法可以避免这种工作/黑客,从而使其成为更高级别(行业级别)的代码?

任何跳出盒子的建议都是欢迎的!


当前回答

对我来说,当(0)是好的时候做{…}。如果您不想看到do{…}while(0),您可以为它们定义替代关键字。

例子:

SomeUtilities.hpp:

#define BEGIN_TEST do{
#define END_TEST }while(0);

SomeSourceFile.cpp:

BEGIN_TEST
   if(!condition1) break;
   if(!condition2) break;
   if(!condition3) break;
   if(!condition4) break;
   if(!condition5) break;
   
   //processing code here

END_TEST

我认为编译器将在二进制版本的do{…}while(0)中删除不必要的while(0)条件,并将断点转换为无条件跳转。您可以检查它的汇编语言版本来确定。

使用goto还可以生成更简洁的代码,并且使用条件-然后跳转的逻辑非常简单。您可以执行以下操作:

{
   if(!condition1) goto end_blahblah;
   if(!condition2) goto end_blahblah;
   if(!condition3) goto end_blahblah;
   if(!condition4) goto end_blahblah;
   if(!condition5) goto end_blahblah;
   
   //processing code here

 }end_blah_blah:;  //use appropriate label here to describe...
                   //  ...the whole code inside the block.
 

注意标签放在结束}之后。这是避免一个可能的问题,在goto中,不小心将代码放在中间,因为你没有看到标签。现在就像do{…}while(0)没有条件代码。

为了让代码更清晰,更容易理解,你可以这样做:

SomeUtilities.hpp:

#define BEGIN_TEST {
#define END_TEST(_test_label_) }_test_label_:;
#define FAILED(_test_label_) goto _test_label_

SomeSourceFile.cpp:

BEGIN_TEST
   if(!condition1) FAILED(NormalizeData);
   if(!condition2) FAILED(NormalizeData);
   if(!condition3) FAILED(NormalizeData);
   if(!condition4) FAILED(NormalizeData);
   if(!condition5) FAILED(NormalizeData);

END_TEST(NormalizeData)

有了这个,你可以做嵌套块,并指定你想要退出/跳出的位置。

BEGIN_TEST
   if(!condition1) FAILED(NormalizeData);
   if(!condition2) FAILED(NormalizeData);

   BEGIN_TEST
      if(!conditionAA) FAILED(DecryptBlah);
      if(!conditionBB) FAILED(NormalizeData);   //Jump out to the outmost block
      if(!conditionCC) FAILED(DecryptBlah);
  
      // --We can now decrypt and do other stuffs.

   END_TEST(DecryptBlah)

   if(!condition3) FAILED(NormalizeData);
   if(!condition4) FAILED(NormalizeData);

   // --other code here

   BEGIN_TEST
      if(!conditionA) FAILED(TrimSpaces);
      if(!conditionB) FAILED(TrimSpaces);
      if(!conditionC) FAILED(NormalizeData);   //Jump out to the outmost block
      if(!conditionD) FAILED(TrimSpaces);

      // --We can now trim completely or do other stuffs.

   END_TEST(TrimSpaces)

   // --Other code here...

   if(!condition5) FAILED(NormalizeData);

   //Ok, we got here. We can now process what we need to process.

END_TEST(NormalizeData)

杂乱的代码不是goto的错,而是程序员的错。不使用goto仍然可以生成意大利面条代码。

其他回答

我就是这么做的。

void func() {
  if (!check()) return;
  ...
  ...

  if (!check()) return;
  ...
  ...

  if (!check()) return;
  ...
  ...
}
typedef bool (*Checker)();

Checker * checkers[]={
 &checker0,&checker1,.....,&checkerN,NULL
};

bool checker1(){
  if(condition){
    .....
    .....
    return true;
  }
  return false;
}

bool checker2(){
  if(condition){
    .....
    .....
    return true;
  }
  return false;
}

......

void doCheck(){
  Checker ** checker = checkers;
  while( *checker && (*checker)())
    checker++;
}

怎么样?

I'm adding an answer for the sake of completeness. A number of other answers pointed out that the large condition block could be split out into a separate function. But as was also pointed out a number of times is that this approach separates the conditional code from the original context. This is one reason that lambdas were added to the language in C++11. Using lambdas was suggested by others but no explicit sample was provided. I've put one in this answer. What strikes me is that it feels very similar to the do { } while(0) approach in many ways - and maybe that means it's still a goto in disguise....

earlier operations
...
[&]()->void {

    if (!check()) return;
    ...
    ...
    if (!check()) return;
    ...
    ...
    if (!check()) return;
    ...
    ...
}();
later operations

我不是c++程序员,所以我不会在这里写任何代码,但到目前为止还没有人提到面向对象的解决方案。下面是我的猜测:

拥有一个通用接口,该接口提供了一个方法来评估单个条件。现在,您可以在包含有问题的方法的对象中使用这些条件的实现列表。遍历列表并计算每个条件,如果其中一个条件失败,可能会提前爆发。

这样的设计很好地遵循了开/闭原则,因为在初始化包含相关方法的对象时,可以很容易地添加新的条件。您甚至可以向接口添加第二个方法,该方法用于条件评估,返回条件的描述。这可以用于自文档系统。

但是,缺点是由于使用了更多的对象和遍历列表,所涉及的开销稍微多一些。

首先,一个简短的例子来说明为什么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函数接受大量参数,但如果您的类/结构具有良好的内聚性,这通常不是问题。(也就是说,属于一起的东西生活在一个对象中,所以这些函数可以作为指向少量对象的指针或引用,但仍然可以做很多有用的工作。)