我刚刚在代码中修复了一个非常微妙的错误,这是由异常切片引起的,现在我想确保我完全理解发生了什么。
这是我们的基本异常类,派生类和相关函数:
class Exception
{
public:
// construction
Exception(int code, const char* format="", ...);
virtual ~Exception(void);
<snip - get/set routines and print function>
protected:
private:
int mCode; // thrower sets this
char mMessage[Exception::MessageLen]; // thrower says this FIXME: use String
};
class Derived : public Exception {
public:
Derived (const char* throwerSays) : Exception(1, throwerSays) {};
};
void innercall {
<do stuff>
throw Derived("Bad things happened!");
}
void outercall {
try {
innercall();
}
catch(Exception& e)
{
printf("Exception seen here! %s %d\n", __FILE__, __LINE__);
throw e;
}
}
错误当然是外部调用最终会抛出异常,而不是派生。我的错误是由于调用堆栈中的高位尝试捕获Derived失败而导致的。
现在,我只想确保理解 - 我相信在'throw e'行,使用默认的复制构造函数创建一个新的Exception对象。这是真的发生了什么?
如果是这样,我是否可以锁定将被抛出的对象的复制构造函数?我真的更喜欢这种情况不会再次发生,我们的代码没有理由复制Exception对象(我知道)。
请不要评论我们有自己的异常层次结构这一事实。这是一个旧的设计,我正在努力纠正(我正在取得良好的进展。我已经摆脱了本土的字符串类,以及许多本土容器。)
更新:要明确的是,在我提出问题之前,我已经修复了错误(通过将'throw e'更改为'throw')。我只是想确认发生了什么。
答案 0 :(得分:21)
当你抛出一个物体时,你实际上是在扔一个物体的副本,而不是原件。想一想 - 原始对象在堆栈中,但堆栈正在展开并失效。
我认为这是标准的一部分,但我没有副本可供参考。
catch块中抛出的异常类型是catch的基本类型,而不是抛出的对象的类型。绕过这个问题的方法是throw;
而不是throw e;
,它将抛出原始捕获的异常。
答案 1 :(得分:10)
A quick google表示是的,你要求复制构造函数是必需的并且必须是公开的。 (这是有道理的,因为你正在初始化e
的副本并扔掉它。)
无论如何,只需使用throw
而不指定异常对象,重新抛出catch
中捕获的内容。不应该解决这个问题吗?
catch(Exception& e)
{
printf("Exception seen here! %s %d\n", __FILE__, __LINE__);
throw;
}
答案 2 :(得分:7)
是
throw e;
无论e
实际是什么,都会引发{em>静态类型e
的异常。在这种情况下,使用复制构造函数将Derived
异常复制到Exception
。
在这种情况下你可以
throw;
正确处理Derived
异常气泡。
如果您对某些其他情况下的多态投掷感兴趣,请参阅always so useful C++ FAQ Lite。
答案 3 :(得分:1)
C ++永远不会让我感到惊讶。如果这是对这种行为的赌注,我会失去很多钱!
首先将异常对象复制到临时对象,您应该使用throw
。引用标准15.1 / 3:
throw-expression初始化一个临时对象,称为异常对象,其类型是通过从throw的操作数的静态类型中删除任何顶级cv限定符并从“T的数组”调整类型来确定的。 “或”函数分别将T“返回”指向T“或”指向函数返回T的指针“。
我认为这会产生一个非常有用的编码标准规则:
异常层次结构的基类应具有纯虚析构函数。
或
异常层次结构中基类的复制构造函数应受到保护。
要么实现编译器在尝试“抛出e”时会发出警告的目标,因为在第一种情况下,您无法创建抽象类的实例,第二种情况是因为您无法调用复制构造函数。