5

介绍

这个问题来自这个问题:命名循环成语:危险?. 对于不想阅读原始问题的人来说,这是关于做这样的事情:

named(label1) for(int i = 0 ; i < 10 ; i++) {
    for(int j = 0 ; j < 10 ; j++) {
      if(some_condition)
            break(label1); // exit outer loop
      }
}

这个新问题是关于“命名循环”习语的改进版本。如果你懒得看整篇文章,你可以直接到这篇文章的“示例”部分清楚地理解我在说什么。

设计缺陷

不幸的是,这个问题很快就被关闭了(后来又被重新打开),因为它更像是一个利弊辩论,而不是一个纯粹的技术问题。它似乎不适合 SO Q&A 格式。此外,我提供的代码有几个缺陷:

  • 关键字break由宏重新定义
  • 宏是用小写字母编写的
  • 它使一些可怕的东西可以编译(至少使用 MSVC):

    int foo() {
    
       named(label1) for(int i = 0 ; i < 10; i++)
       {
          if(some_condition)
          {
              break(label1);  // here it's ok, the behavior is obvious
          }   
       }
    
       break(label1); // it compiles fine without warning... but the behavior is pretty obscur!
    
    }
    
  • 它可能会破坏一些好看的代码。例如,由于范围问题,以下内容无法编译。

    int foo() {
    
    named(label1)  for(int i = 0 ; i < 10 ; i++)
           named(label2)  for(int j = 0 ; j < 10 ; j++)
            if(i*j<15)
                cout << i*j << endl;
            else
                break(label2);
     }
    

更安全的实施

我试图解决所有这些问题,以获得命名循环的安全版本。更一般地说,它也可以称为可破坏作用域,因为它可以用于提前退出任何作用域,而不仅仅是循环。

这是两个宏NAMED和的定义BREAK

    #define NAMED(bn)   if(const bool _YOU_CANNOT_USE_NAMED_BREAK_IF_YOU_ARE_OUTSIDE_##bn##_ = true)\
                            goto _named_loop_identifier__##bn##_;\
                        else\
                            _break_the_loop_labelled_##bn##_:\
                            if(true)\
                                {}\
                            else\
                                if(! _YOU_CANNOT_USE_NAMED_BREAK_IF_YOU_ARE_OUTSIDE_##bn##_)\
                                    goto _break_the_loop_labelled_##bn##_;\
                                else\
                                    _named_loop_identifier__##bn##_:\


    #define BREAK(bn) if(!_YOU_CANNOT_USE_NAMED_BREAK_IF_YOU_ARE_OUTSIDE_##bn##_){} \
                        else goto _break_the_loop_labelled_##bn##_

它看起来又丑又麻烦,因为它还避免了一些可能由 MSVC 或 GCC 生成的警告,例如“未使用的变量”、“未引用的标签”或“建议显式大括号”。此外,如果使用不正确,它不应该编译,在这种情况下,错误消息将是可以理解的。例如 :

    NAMED(loop1) for(int i = 0 ; i < 10; i++) {
        NAMED(loop2) for(int j = 0 ; j < i ; j++) {
            cout << i << "," << j << endl;
            if(j == 5) {
                BREAK(loop1);   // this one is okay, we exit the outer loop
            }
        }
        BREAK(loop2); // we're outside loop2, this is an error
    }

前面的代码将无法编译,并且第二个代码的编译器错误消息BREAK:'_YOU_CANNOT_USE_NAMED_BREAK_IF_YOU_ARE_OUTSIDE_loop2_`,非常明确。

例子

在问我的问题之前,我提供了两个例子来说明这些结构的(相对)有用性:

break外循环:

     NAMED(myloop) for(int i = 0 ; i < rows; i++) {
         for(int j = 0 ; j < cols ; j++) {
            if(some_condition) {
                 BREAK(myloop);
            }
         }
     }

退出特定范围:

NAMED(myscope1) {
    cout<< "a";
    NAMED(myscope2)
    {
        cout << "b";
        NAMED(myscope3)
        {
            cout << "c";
            BREAK(myscope2);
            cout << "d";
        }
        cout << "e";
    }
    cout <<"f";
}

此代码打印:abcf

我的问题

在定义我的问题是什么之前,我必须先定义它不是什么,因为我不想看到我的话题在 10 分钟内结束。

不是“这是个好主意吗?”,也不是“你觉得怎么样?” 甚至“有用吗?”,因为 stackoverflow 似乎不是一个讨论场所。无论如何,我已经知道答案:“宏是邪恶的”、“Goto 是邪恶的”和“改用 lambdas”。

相反,我的问题是关于编程错误的技术正确性和鲁棒性。我希望这个结构尽可能安全。

  • 用户是否有可能滥用它并且仍然能够编译? 我试图修复原始实现的明显问题,但 C++ 非常复杂,也许我错过了什么?

  • 它会默默地破坏一些好看的代码吗?这是我主要关心的问题。它会干扰其他 C++ 功能(异常处理、析构函数调用、其他条件语句或其他任何东西......)吗?

我的目标是证明这种结构本质上并不危险。我已经知道在实际代码中使用它是一个非常糟糕的主意,因为其他程序员可能不清楚它,但是在个人项目中使用它足够安全吗?

编辑:布尔变量现在是const(感谢 Jens Gustedt)。稍后我将尝试将 s 替换为ifsfor以检查它是否可以在像这样使用时消除虚假警告:

if(true)
     BREAK(label);

EDIT2:正如 JensGustedt 所注意到的,ifC(仅限 C++)中不允许在语句中声明变量。用循环替换它的另一个原因。

4

2 回答 2

2

我假设您已经对此进行了测试,但是......bool您声明的变量NAMED(bn)在同一块中是否仍然可用,它声明的 if 语句是否存在?(:除非是这样,否则您的成语将无法使用。:)

我可以看到这是安全的:

{
    NAMED(one) { ... }
}
BREAK(one);

编译器会大惊小怪就BREAK(nb);

但这看起来仍然不安全:

{
    NAMED(bn) { ... }
    BREAK(bn);
}

从某种意义上说,编译器仍然可以接受定义的变量而不是大惊小怪,这是不安全的。但它可能会形成一个无限循环,从而默默地破坏你的程序。

-杰西

PS:它不会干扰,try..finally因为finally无论您如何退出该try块,该块被定义为执行。因此,只要您不试图避免finally阻塞,就可以了。

P(PS)S:我看到的与其他构造的唯一真正奇怪的交互是:

#if DEBUG
    NAMED(bn)
#endif
    while(true)
    {
        BREAK(BN);
    }

这是病态的!;-D 这将在 DEBUG 中正常编译,但在 RELEASE 中会出现编译器错误。

于 2012-06-17T15:36:22.063 回答
1

我真的无法回答 C++ 方面的问题,但既然你也将它标记为 C ...

通常对这样的事情使用if/else构造并不是一个好主意,因为你总能找到一个好意的 C 编译器,它会为奇怪的else匹配和类似的事情找到适当的警告。

我通常对P99for使用类似的构造。他们避免悬空(或关于它的虚假警告)。也是C 中唯一可以按您需要的方式放置局部变量的地方,它们不允许在 C++ 中使用,也不允许在 C++ 中使用。elseforifwhile

在“安全”方面,您可能应该声明变量register bool const而不仅仅是bool. 因此,没有人可以尝试更改它,甚至可以背着它的地址更改它。

for但是对于您使用它的特定目的,我并不太喜欢goto。你真正在做的是展开堆栈。在 C 语言中,有一个结构可以正确地做到这一点,即setjmp/ longjmp。在 C++ 中,它可能是try/ catch

于 2012-06-17T15:31:24.497 回答