22

想象两段相似的代码:

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;
}

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

4

2 回答 2

29

根据您安排异常层次结构的方式,通过在 throw 语句中命名异常变量来重新抛出异常可能会分原始异常对象。

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

例如

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外部块捕获的异常中。

于 2009-09-26T18:01:33.063 回答
17

在根据 C++ 标准 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) 或 return 语句的操作数中被完全视为函数参数。

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

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

于 2009-09-26T17:00:25.683 回答