在条件语句中声明的变量作用域

9

一些重构导致了一段代码,进而引发了这个最小化的测试用例:

int main () {
    if (int i=1) {
        /* IF-BLOCK */
    } else {
        throw i;
    }
}

这段代码可以正常编译。然而,我一直认为i只在IF-BLOCK中可见,但它似乎不是这样的。这是编译器的一个bug吗?

此外,为什么下面的代码也能正常工作?

int main () {
    if (int i=1) {
    } else if (int i=2) {
    } else {
        throw i;
    }
}

请注意第二个if语句中重新声明了i,这是另一个编译器的错误吗?

1
这是C++中非常有用的功能。我认为人们主要使用场景是像 if (auto p = dynamic_cast<Foo*>(q)) { /* ... use p ... */ } 这样的情况,或者任何可能创建空指针的情况。 - Kerrek SB
1个回答

15

不,这实际上是正确的行为。

6.4选择语句 [stmt.select]

在条件中声明的名称(由条件的type-specifier-seq或声明符引入)从其声明点起到受控制子语句结束时处于作用域内。如果该名称在受控制子语句的最外层块中被重新声明,则重新声明该名称的声明是非法的。 [例子:

if (int x = f()) {
    int x; // ill-formed, redeclaration of x
}
else {
    int x; // ill-formed, redeclaration of x
}

——示例结束]

(着重标记为我的部分)

这基本意味着 i 的作用域从条件开始,在 if 块结束之后才结束,在此期间,else 块也是 if 块的一部分。

你对嵌套 if 的第二个问题基于(错误的)假设,即 else-if 是引导 if 的一部分,但实际上并不是。 if (int i = 2) 是第一个 else 的主体!

     if (int i=1)
          |
         / \
        /   \
       /     \
      /       \
     /         \
   if-block   else
               |
           if(int i=2)
             /    \
            /      \
           /        \
       if-block   throw i

这反过来意味着什么:

int main () {
    if (int i=1) {    
    } else if (1) {
        throw (i+2);
    } else {
        throw i;
    }
}

这段代码是有效的,因为i声明在throw (i+2);中可见,但在嵌套作用域中隐藏第一个i仍然是有效的,因为在嵌套作用域中,名称可以被覆盖:


int main () {
    if (int i=1) {    
    } else if (int i=2) {
        throw (i+2); // now refers to `int i=2`
    } else {
        throw i;
    }
}

总的来说,不要惊慌:使用最后一条语句中找到的模式编写分词器或解析器等仍然有效,这里的相关新知识是条件中的任何声明跨越整个if树,但可以在任何嵌套的if中被覆盖。

此外,请放心,以下内容仍然无效(即使在旧编译器中它是有效的):

if (int i=0) {}
std::cout << i; // nope, not valid

1
+1我从未想过这个,很好知道。这背后有什么故事吗?还是你只是因为学术兴趣而想分享一些挖掘出来的东西? - Hulk
@Hulk:我在重构一些业余代码时发现了这个问题,即使我删除了变量的声明,在去往真正的工作场所之前,它仍然是可访问的 :). 你可以在这里看到实际的更改: https://github.com/phresnel/excygen/commit/cafbe02a9d0d598b3455c80444fe2978f52716c1#L0L138。最初只有 if (auto oit = operator_(it, end)),然后我通过类似于 if (*it == '/') 的方式将其删除,但在其块内部,它仍然访问 oit,它编译通过,但我得到了一个未初始化的 optional<> 异常,这就是我发现并想分享的内容 :) - Sebastian Mach

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接