【问题标题】:Why does the Try-Catch block affect a variable in an enclosing scope?为什么 Try-Catch 块会影响封闭范围内的变量?
【发布时间】:2019-08-02 17:36:31
【问题描述】:

为什么外部的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 编译我的代码。

【问题讨论】:

  • 似乎可以正常工作,并且与 clang 7 一样。可使用gcc 8 重现,将temp 声明为const std::string temp("exception"); 似乎可以解决此问题。
  • 我可以用 "gcc (GCC) 7.4.0" 重现它,cygwin 版本
  • 似乎与刷新输出有关的问题.. 我会在 GCC 上尝试一下并为您发布答案
  • @lubgr 谢谢它的工作。
  • 我也可以在 GCC 8.3,MSYS2 版本上重现。

标签: c++ try-catch


【解决方案1】:

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

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

这种复制/移动操作的省略,称为复制省略,是 在以下情况下允许(可以结合到 消除多个副本):

  • 在 throw 表达式中,当操作数是非易失性自动对象的名称时(函数或 catch 子句除外) 参数)其范围不超出 最里面的封闭try-block(如果有的话),复制/移动 从操作数到异常对象的操作可以省略 将自动对象直接构造到异常对象中

在以下复制初始化上下文中,移动操作可能 用于代替复制操作:

  • 如果 throw 表达式的操作数是非易失性自动对象的名称(函数或 catch 子句参数除外) 其范围不超出最里面的封闭 try 块的末尾(如果有)

这是一系列优化,允许避免或尽可能高效地完成异常对象的复制初始化。现在,std::string 移动构造的常见实现是将源字符串留空。这似乎正是您的代码发生的情况。外部作用域中的temp 被移出(并留空)。

但这不是预期的行为。 temp 的范围超过(到目前为止)它被抛出的 try 块。所以 GCC 没有业务对其应用复制省略。

一种可能的解决方法是将temp 的声明放在while 循环中。这在每次迭代时都会初始化一个新的 std::string 对象,因此即使 GCC 离开它,它也不会被注意到。

cmets 中提到了另一种解决方法,即使外部 temp 成为 const 对象。这将强制进行复制(因为移动操作需要非常量源对象)。

【讨论】:

    【解决方案2】:

    我不确定它是否是另一个答案中提到的错误,但是在处理异常一次后,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;
        }
    

    【讨论】:

    • 在您写答案前一个小时,在评论和@StoryTeller 的回答中都提到了这个解决方案。
    • 信不信由你,我并没有真正看到那些 cmets 并开始使用此代码,一旦它起作用,我就发布了我的答案。在发布我的答案之前,以阅读所有 cmets 为教训。感谢您的反对票。
    猜你喜欢
    • 2012-07-24
    • 1970-01-01
    • 2015-12-05
    • 1970-01-01
    • 2011-07-25
    • 2010-09-10
    • 2011-02-20
    • 2011-11-24
    相关资源
    最近更新 更多