【问题标题】:What is the difference between throw and throw with arg of caught exception?throw 和 throw 与捕获异常的 arg 有什么区别?
【发布时间】:2010-12-01 16:23:49
【问题描述】:

想象两段相似的代码:

try {
  [...]
} catch (myErr &err) {
  err.append("More info added to error...");
  throw err;
}

try {
  [...]
} catch (myErr &err) {
  err.append("More info added to error...");
  throw;
}

这些实际上是相同的,还是在某些微妙的方面有所不同?例如,第一个是否会导致运行复制构造函数,而第二个可能会重用相同的对象来重新抛出它?

【问题讨论】:

    标签: c++ exception try-catch throw


    【解决方案1】:

    根据您如何安排异常层次结构,通过在 throw 语句中命名异常变量来重新抛出异常可能切片原始异常对象。

    无参数 throw 表达式将抛出当前异常对象并保留其动态类型,而带参数的 throw 表达式将根据 @987654321 的参数的 static 类型抛出一个新异常@。

    例如

    int main()
    {
        try
        {
            try
            {
                throw Derived();
            }
            catch (Base& b)
            {
                std::cout << "Caught a reference to base\n";
                b.print(std::cout);
                throw b;
            }
        }
        catch (Base& b)
        {
            std::cout << "Caught a reference to base\n";
            b.print(std::cout);
        }
    
        return 0;
    }
    

    如上所写,程序会输出:

    捕获了对 base 的引用
    衍生的
    捕获了对 base 的引用
    基地

    如果throw b 被替换为throw,那么外部catch 也会捕获最初抛出的Derived 异常。如果内部类通过值而不是通过引用捕获 Base 异常,这仍然成立 - 尽管这自然意味着原始异常对象无法修改,因此对 b 的任何更改都不会反映在 Derived外部块捕获的异常。

    【讨论】:

    • 啊,我完全忘记了切片!该死,这很重要!感谢您提出这个问题。 +1(虽然我认为当您写“...保留原始静态类型...”时,您的意思是 dynamic 类型。毕竟,如果不是“原始静态类型”。)-
    • 很好的答案,我也完全忘记了。
    • 我很高兴其他人遇到了 slicing 问题 ;)
    • +1 用于提及切片。但是“……保留原来的静态类型……”——你不是说动态类型吗?
    • @sellibitze:嗯,它是初始抛出表达式的原始静态类型(这是我最初想到的),或者是原始抛出对象的动态类型,这是更准确的说法。我会编辑。
    【解决方案2】:

    在第二种情况下,根据 C++ Standard 15.1/6 不使用复制构造函数:

    没有操作数的 throw 表达式会重新抛出正在处理的异常。使用现有临时重新激活异常;没有创建新的临时异常对象。 不再认为该异常已被捕获;因此,uncaught_exception() 的值将再次为真。

    在第一种情况下,将根据 15.1/3 抛出新异常:

    throw-expression 初始化一个临时对象,称为异常对象,其类型是通过从 throw 操作数的静态类型中删除任何顶级 cv 限定符并调整来确定的 从“T 数组”或“返回 T 的函数”到“指向 T 的指针”或“指向返回 T 的函数的指针”的类型, 分别。 <...> 临时用于初始化匹配处理程序(15.3)中命名的变量。 throw-expression 的类型不应是 不完整类型,或指向不完整类型的指针或引用,除了 void*、const void*、 volatile void* 或 const volatile void*。除了这些限制和限制 15.3 中提到的类型匹配,throw 的操作数在调用中被完全视为函数参数 (5.2.2) 或返回语句的操作数。

    在这两种情况下,throw 阶段 (15.1/5) 都需要复制构造函数:

    当抛出的对象是一个类对象,并且用于初始化临时副本的复制构造函数不可访问时,程序是非良构的(即使临时对象可以被消除)。 类似地,如果该对象的析构函数不可访问,则程序格式错误(即使临时对象可以被消除)。

    【讨论】:

    • 因为无论如何都需要对最初抛出的异常访问复制 ctor,所以我认为在这种情况下这不是问题。但你的仍然是一个很好的答案。 +1
    • 是的,但在第一种情况下,copy c-tor 将被使用两次。
    • 它说明了声明何时指定了类类型。然而,在他的例子中,它指定了一个引用类型:) 引用将直接绑定并引用异常对象。因此,我们仅在抛出点需要一个复制 ctor,在这种情况下不捕获(例如,当基本复制构造函数受到保护时,它会有所不同)。
    • 我错过了他在异常声明中使用引用类型。固定。
    猜你喜欢
    • 2020-12-20
    • 2012-02-27
    • 1970-01-01
    • 2018-02-27
    • 2019-12-15
    • 2020-12-04
    • 1970-01-01
    相关资源
    最近更新 更多