异常切片 - 这是由于生成的复制构造函数?

时间:2009-07-07 22:35:18

标签: c++ exception object-slicing

我刚刚在代码中修复了一个非常微妙的错误,这是由异常切片引起的,现在我想确保我完全理解发生了什么。

这是我们的基本异常类,派生类和相关函数:

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')。我只是想确认发生了什么。

4 个答案:

答案 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”时会发出警告的目标,因为在第一种情况下,您无法创建抽象类的实例,第二种情况是因为您无法调用复制构造函数。