15

我读过一篇关于 C++ 中“命名循环成语”的文章:http ://en.wikibooks.org/wiki/More_C%2B%2B_Idioms/Named_Loop

这个成语允许我们这样写:

named(outer) 
for(int i = 0 ; i < rows ; ++i) {

   named(inner) 
   for(int j = 0 ; j < cols ; ++j) {

        if(some_condition)
            break(outer);   // exit the 'outer' loop 

   }
}

这种结构已经作为许多语言的核心特性存在,例如 Java。

根据文章,它可以通过定义两个邪恶的宏在 C++ 中实现:

#define named(blockname) goto blockname; \
                         blockname##_skip: if (0) \
                         blockname:

#define break(blockname) goto blockname##_skip;

我知道很多人想禁止使用goto. 我个人发现它在极少数情况下很有用,尤其是当我想要break一堆嵌套循环时。在我看来,这个成语是一个更干净的解决方案,但是可以在实际代码中使用它吗?

在文章的讨论页面上,可以阅读:

“不要这样做。你会下地狱的”

所以我的问题是:使用命名循环习语有什么缺点?危险吗 ?如果是,为什么?

额外的问题:是否可以实现continue类似的命名?(我认为使用语法是不可能的named(...) for(...;...;...) {},但谁知道呢?)

编辑:我同意你的看法,重新定义关键字很讨厌。用什么#define breakLoop()代替呢?

4

2 回答 2

10

正如评论中所述,#definingbreak是有问题的。假设您使用其他东西。

我仍然认为这是危险的。这是一个非常不寻常的习语(对 C++ 程序员而言),因此他们不太可能理解,因此他们可能会做出重大更改。鉴于完成同一件事的方式不那么令人惊讶,因此也不那么危险,我建议不要这样做。

考虑将循环放在函数或 lambda 中。然后你可以return打破外循环。作为一个好处,您可以返回有关过早退出的信息,这可能对外部代码有用。

于 2012-06-12T23:01:55.280 回答
2

我发现这有几个问题。

首先,您要定义一个与语言的保留字之一同名的宏。即使您的编译器对此没有抱怨,它也很容易出错并且不会(至少在 IMO)很危险。

其次,我总是对以编程方式创建标签犹豫不决。即使您的编译器可能会抱怨如果您不小心在同一范围内创建了两个具有相同名称的标签,但如果程序员不剖析这些宏(这部分违背了额外抽象的目的),它生成的错误消息可能不会很容易理解.

可能我的主要问题是宏引入了一些与普通语言语法不同的东西。这些named(...)行不以分号结尾,也不是后面跟着一个{ ... }块。添加任何类型的新语法都会为开发人员的困惑和意外误用打开大门。

总的来说,我有点喜欢命名循环的想法,但这不是您想要使用宏创建的那种东西。这是一种真正需要由语言本身提供的机制。使用 C 或 C++ 时,使用手动创建的标签和goto. 明确的几乎总是比隐藏宏背后发生的事情要好。

于 2012-06-12T23:00:29.700 回答