“滥用”循环以减少if-nesting

时间:2015-10-26 10:04:40

标签: c++

有时必须执行一系列if / else检查。在过去的日子里,goto是为此而准备的沙漠仪器。

由于goto在许多代码风格指南中是禁止的,我有时会使用循环代替。例如:

do
{
  if (a)
  {
   doA();
   break;
  }
  //...
  if (z)
  {
   doZ();
   break;
  }
}while(false);

//all break jump here

这是一个好方法吗?是否有一个好的C ++模式(例如使用模板,继承等)来实现它而不需要太多开销?

5 个答案:

答案 0 :(得分:7)

由于条件似乎无关,else if是一个选项:

if (a) {
    doA();
} else if (b) {
//...
} else if (z) {
    doZ();
}

答案 1 :(得分:5)

通常,对于编写可维护代码,您应该按照预期的方式使用该语言的工具。 你对一个循环进行一次迭代的循环的使用显然是一个偏差,而不是那些首先在goto上皱眉的同样的代码风格指南。

该语言中提供了一些工具,使您可以更轻松地与其他程序员进行沟通,并让您进一步了解您打算在此处做什么。

  • 其中一个是将此代码块外包到一个单独的方法中并使用return语句。有趣的早期回报也被一些人所厌恶。
  • 另一个解决方案可能是使用异常,您尝试/ catch。它是否适合您的目的主要取决于"检查"你在干嘛。
  • 最后,if / else链可能看起来不是很优雅,但另一方面却是一个构造,几乎没有误解的余地。

答案 2 :(得分:2)

使用goto,使用continue,使用多个break,或使用多个return,如果被滥用,可以使用不同风格的意大利面条编码,所有这些都可以使用创建非条件分支。虐待的一些例子:

  • goto被视为非常糟糕如果用于除跳跃之外的任何其他内容,例如用于错误处理程序。 (虽然这可能是goto的可接受用途,但它仍然开始了整个被殴打死马的goto被认为是有害的辩论,所以我仅仅因为这个原因而避免goto 。)

  • continue无条件地向上跳跃,这被认为是错误的:意大利面条代码的一个定义是无条件地向上和向下跳跃的代码。将多个continue添加到同一循环时尤其糟糕。通常,continue的存在是一个需要重写的循环的非常确定的标志。

  • 可以滥用多个break和多个return来突破复杂的嵌套循环,使代码难以阅读和维护。此外,问题中演示的break方法强制使用稍微模糊的do-while(false)循环。

通常,所有这些方法都被认为是不好的做法,因为它们很容易被滥用。你只需选择最不好的那个:换句话说,最可读和最不模糊。

我相信多个returns是最易读的形式,因为它可以与一些函数结果变量混合使用,无论如何你都可能想要它:

result_t func ()
{
  if (a)
  {
    doA();
    return RESULT_A;
  }

  ...  // you'll need lots of if statements here to justify this program design

  if (z)
  {
    doZ();
    return RESULT_Z;
  }

  return RESULT_NORMAL;
}

关于函数内部资源分配的附注。

如果上面的函数需要释放一些已分配的资源并且它总是需要这样做,你应该用RAII来做,这样当局部变量超出范围时,它会自行清理。

如果它只需要在某些情况下释放一些已分配的资源(例如在出错时),那么你应该在每个if语句(不可维护)中执行此操作,也不应该实现某些1980年代的BASIC程序员的“错误转到”。相反,考虑在main函数之外添加一个包装函数,以使程序可维护并最小化混乱:

result_t wrapper ()
{
  stuff = allocate(); // if needed

  result_t result = func(stuff);

  if(result == BAD)
  {
    deallocate(stuff);
  }

  return result;
}

答案 3 :(得分:1)

你的非循环对你的程序员来说有点侮辱,但并非如此。在断言宏和记录器的实现中你会看到很多这样的代码。

当然,使用模板,滥用的机会几何扩展。例如,你可以写这种东西:

void doA() {
    std::cout << "A" << std::endl;
}

void doB() {
    std::cout << "B" << std::endl;
}



void either_or() {}

template<class T>
void either_or(T&& t) {
    if (std::get<bool>(t)) {
        std::get<void(*)()>(t)();
    }
}

template<class T, class...Ts>
void either_or(T&& t, Ts&&... ts)
{
    if (std::get<bool>(t)) {
        std::get<void(*)()>(t)();
    }
    else {
        either_or(std::forward<Ts>(ts)...);
    }
}

auto main() -> int
{
    using namespace std;

    bool a = false;
    bool b = true;

    either_or(make_tuple(a, &doA),
              make_tuple(b, &doB));

    return 0;
}

虽然执行相应标志为真的第一个操作的一种有效方式(在优化者干预之后)对维护者来说可能更具辱骂性。

答案 4 :(得分:-1)

恕我直言,我认为使用goto时没有任何错误。 如果您知道,使用goto的方式,地点和原因,您的代码可能会更清晰,更易读。

例如,Linux使用了很多goto语句。正如@Bartek Banachewicz所述,“Linux代码已经有了这个事实并不意味着他们是不那么糟糕。“

goto并不总是坏的,它们有许多有效的用途,比如打破深度嵌套的循环。在这种情况下使用goto将呈现更清晰的代码。只有在被滥用或使用时才会导致意大利面条代码,而这些代码在C ++程序中并不相同。

  

注意:不必要地使用任何循环,if-else语句,break,continue   什么不是,将导致凌乱和无法管理的代码。即使我们   让这个世界成为un-goto的地方,意大利面条代码将存在。它的   个人以确保他/她写一个更清洁,可读,   最佳代码。

最好的设计是在C ++程序中使用exceptions

如果您不能选择使用例外,那么另一种设计可能是使用另一种方法,其中多个案例组合在一起。