想象一下两个类似的代码:
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;
}
这些是否实际相同,或者它们是否以某种微妙的方式不同?例如,第一个是否导致复制构造函数运行,而第二个可能重用相同的对象来重新抛出它?
答案 0 :(得分:28)
根据排列异常层次结构的方式,通过在throw语句中命名异常变量来重新抛出异常,可以 slice 原始异常对象。
无参数抛出表达式将抛出当前异常对象,保留其动态类型,而带有参数的throw表达式将基于{{>> 参数的 static 类型抛出新异常{{ 1}}。
E.g。
throw
如上所述,程序将输出:
Caught a reference to base Derived Caught a reference to base Base
如果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;
}
替换为throw b
,则外部catch也将捕获最初抛出的throw
异常。如果内部类通过值而不是通过引用捕获Derived
异常,这仍然成立 - 尽管这自然意味着无法修改原始异常对象,因此对Base
的任何更改都不会反映在外部块捕获的b
异常。
答案 1 :(得分:16)
在根据C ++标准15.1 / 6的第二种情况下,不使用复制构造函数:
没有操作数的throw-expression重新抛出正在处理的异常。使用现有临时值重新激活该例外;没有创建新的临时异常对象。异常不再被视为被捕获;因此,uncaught_exception()的值将再次为真。
在第一种情况下,将根据15.1 / 3抛出新的异常:
throw-expression初始化一个临时对象,称为异常对象,其类型是通过从throw和adjust的操作数的静态类型中删除任何顶级cv限定符来确定的。 从“T的数组”或“返回T的函数”到“指向T的指针”或“返回T的函数的指针”的类型, 分别。 &LT; ...&GT;临时用于初始化匹配处理程序(15.3)中命名的变量。 throw-expression的类型不应该是 不完整的类型,或指向不完整类型的指针或引用,而不是void *,const void *, volatile void *,或const volatile void *。除了这些限制和限制 在15.3中提到的类型匹配,throw的操作数被完全视为调用中的函数参数 (5.2.2)或退货声明的操作数。
在这两种情况下,在投掷阶段(15.1 / 5)都需要复制构造函数:
当抛出的对象是类对象,并且无法访问用于初始化临时副本的复制构造函数时,程序格式不正确(即使临时对象可能被删除)。 同样,如果该对象的析构函数不可访问,则程序格式错误(即使临时对象可能被删除)。