为什么Try-Catch块会影响封闭作用域中的变量?

40

为什么在捕获第一个异常后外部的 temp 变为空?

#include <iostream>
int main()
{
    std::string temp("exception");
    int value;
    while(std::cin>> value && value != 0)
    {
         try{
              if(value > 9) throw temp;
              else std::cout << value << "\n";
            }
         catch(std::string temp)
         {
              std::cout << temp << "\n";
         }
    }

    return 0;
}

输入:

1
2
11
13

输出:

1
2
exception
// Printing Empty string

预期输出:

1
2
exception
exception

我使用g++ 7.3.0编译我的代码。


5
在使用 clang 7 时,似乎一切正常且符合预期。在使用 gcc 8 时可以重现问题,将 temp 声明为 const std::string temp("exception"); 可以解决此问题。 - lubgr
我可以使用 "gcc (GCC) 7.4.0",cygwin版本来复现它。 - Gojita
1
似乎与刷新输出有关的问题...我会在GCC上尝试并为您发布答案。 - Marzouk
我也可以在GCC 8.3和MSYS2版本上重现。 - HolyBlackCat
该问题在几年前已经报告过了:https://gcc.gnu.org/bugzilla/show_bug.cgi?id=57533 - chtz
显示剩余3条评论
2个回答

40

这似乎是GCC中复制省略实现的一个错误。C++标准如下所述:

[class.copy.elision] (我强调)

在以下情况下,允许省略复制/移动操作(可以组合使用以消除多个副本):

  • 在 throw 表达式中,当操作数是非易失性自动对象(除函数或 catch 子句参数之外)的名称时,其作用域未延伸至最内层封闭 try 块的结尾(如果有的话),从操作数到异常对象的复制/移动操作可以通过直接将自动对象构造到异常对象中来省略。

在以下复制初始化上下文中,可能会使用移动操作而不是复制操作:

  • 如果 throw 表达式的操作数是非易失性自动对象(除函数或 catch 子句参数之外)的名称,其作用域未延伸至最内层封闭 try 块的结尾(如果有的话)

这是一系列优化技术的家族,它允许异常对象的复制初始化被避免或者尽可能地高效。现在,std::string 移动构造的常见实现是将源字符串留空。这似乎恰好是发生在你的代码中的情况。外部作用域的 temp 被移动并且变为空。

但这不是预期的行为。你抛出的 temp 的作用域远远超出了它被抛出的 try 块。因此 GCC 没有理由对其进行复制省略。

一个可行的解决方法是将 temp 的声明放在 while 循环内部。这会在每次迭代中初始化一个新的 std::string 对象,因此即使 GCC 从其中移动,也不会有明显的影响。

还有另一个解决方法,就是像评论中提到的那样,将外部的 temp 设为常量对象。这将强制执行复制操作(因为移动操作需要一个非常量源对象)。


-11

我不确定是否像另一个答案中提到的那样是一个 bug,但是 catch 块在处理异常后会改变/省略 temp 的内容。以下代码解决了这个问题。将 temp 设为 const 即可解决。

#include <iostream>
    int main()
    {
        const std::string temp("exception");
        int value;
        while(std::cin>> value && value != 0)
        {
             try{
                  if(value > 9) throw temp;
                  else std::cout << value << "\n";
                }
             catch(std::string temp){
                  std::cerr << temp << "\n";
                  }
        }

        return 0;
    }

11
在你写下回答的一个小时前,@StoryTeller 在评论和回答中都提到了这个解决方案。 - Christian Severin
3
信不信由你,我并没有真正看到那些评论,只是在成功运行了这段代码后立即发布了我的答案。将其视为一个教训,在发布答案之前阅读所有的评论。感谢你们的投票。 - Manoj

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