C/C++ 中的可破坏命名作用域
Break-able named scopes in C/C++
简介
这个问题由此而来:命名循环成语:危险?对于不想阅读原始问题的人来说,这是关于做这样的事情:
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问答格式。此外,我提供的代码有几个缺陷:
- 关键字
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似乎不是一个讨论的地方。无论如何,我已经知道答案:"Macro是邪恶的","Goto是邪恶的"和"改用lambdas"。
相反,我的问题是关于技术正确性和针对编程错误的鲁棒性。我希望这个结构尽可能安全。
用户是否有可能滥用它并且仍然能够编译?我试图解决原始实现的明显问题,但C++非常复杂,也许我错过了一些东西?
它会默默地破坏一些好看的代码吗?这是我主要关心的问题。它会干扰其他C++功能(异常处理、析构函数调用、其他条件语句或其他任何功能)吗?
我的目标是证明这种结构本质上并不危险。我已经知道在实际代码中使用它是一个非常糟糕的主意,因为其他程序员可能不清楚它,但是在个人项目中使用它是否足够安全?
编辑:布尔变量现在const
(感谢Jens Gustedt)。稍后我将尝试将 if
s 替换为 for
s,以检查它在像这样使用时是否可以删除虚假警告:
if(true)
BREAK(label);
编辑2:正如JensGustedt所注意到的,if
语句中的变量声明在C中是不允许的(仅限C++)。用循环替换它的另一个原因。
我想你已经测试过了,但是......您在 NAMED(bn)
中声明的 bool
变量是否仍然可用 如果声明它的语句存在于同一块中?(:除非是这样,否则你的成语是行不通的。 :)
我可以看到这是安全的:
{
NAMED(one) { ... }
}
BREAK(one);
编译器会在BREAK(nb);
行上大惊小怪
但这看起来仍然不安全:
{
NAMED(bn) { ... }
BREAK(bn);
}
不安全的意义上说,编译器可能仍然接受定义的变量而不是大惊小怪。 但它可能会形成一个无限循环,从而悄无声息地破坏你的程序。
-耶西
PS:它不会干扰try..finally
,因为无论您如何退出try
块,finally
块都被定义为执行。 因此,只要您不试图避免finally
块,您就可以。
P(PS)S:我看到的与其他结构的唯一真正奇怪的交互是这样的:
#if DEBUG
NAMED(bn)
#endif
while(true)
{
BREAK(BN);
}
这是病态的!;-D 这将在调试中编译良好,但在发布中得到编译器错误。
我真的无法回答 c++ 方面,但由于您还将其标记为 C...
通常对这样的事情使用 if
/else
结构并不是一个好主意,因为您总会找到一个善意的 C 编译器,它会为奇怪的else
匹配和类似的事情找到警告。
我通常使用 P99 这样的for
结构。他们避免悬空else
(或关于它的虚假警告)。此外,for
是 C 中唯一可以按照您需要的方式放置局部变量的地方,它们不允许在if
或while
中,因为它们在C++中是
在"安全"方面,您可能应该声明变量register bool const
而不仅仅是bool
。因此,没有人可以尝试更改它,甚至没有人在背后更改它的地址。
但是出于您使用它的特定目的,我也不太喜欢for
或goto
。您真正在做的是展开堆栈。在 C 中,有一个结构可以正确地做到这一点,即 setjmp
/longjmp
。在C++,它可能是try
/catch
.
- 未在作用域中声明unordered_map
- 有没有一种方法可以在编译时获得作用域类名
- C++quit()函数中可能存在作用域问题
- 未在此作用域OpenCV3.4中声明cvSaveImage
- 全局作用域中函数指针的赋值
- 在类函数中初始化外部作用域变量
- 不同作用域中的静态变量和全局变量
- 是同一作用域的函数部分中的函数调用
- 未在此作用域中声明的函数和变量 (C++)
- 类作用域的类型别名"using":[何时]方法中的用法可以先于类型别名?
- 将作用域枚举转换为基础类型
- 表达式必须具有完整或无作用域的枚举图
- 防止作用域枚举可复制/可移动
- 如何在C++中嵌套词法作用域可访问的作用域中声明静态信息?
- 在释放了所有作用域内指针之后仍然可访问内存
- 如何在 c++ 中创建一个可调试的文件作用域(静态?)类
- C/C++ 中的可破坏命名作用域
- 在作用域锁定之前检查可选互斥锁
- 对临时对象的常量引用在函数作用域(生存期)后被破坏
- 是否可以使作用域枚举("enum class")在上下文中可转换为布尔值?