首页 > 解决方案 > 为什么 Try-Catch 块会影响封闭范围内的变量?

问题描述

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

标签: c++try-catch

解决方案


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

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

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

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

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

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

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

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

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

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


推荐阅读