【问题标题】:Exception slicing - is this due to generated copy constructor?异常切片 - 这是由于生成的复制构造函数吗?
【发布时间】:2009-07-07 22:35:17
【问题描述】:

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

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

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

【问题讨论】:

  • 我知道你说过不要担心其他事情,而只是另外两件事:让你的 Exception 类继承自 std::exception 并通过 const &amp; 捕获事物。
  • 捕获为 const 有什么好处?我总是通过引用来捕捉,但为什么是 const 呢? (不是说我现在可以在这门课上做到这一点,而是有一天......)
  • @Kohne:此设置中 const 的优势与任何其他设置中的类似 - 您明确指定您不打算更改对象状态的意图并且编译器应确保你不知道 - const 背后的想法是不可变对象更容易推理(即调试、测试、证明代码正确),因为它们有助于编写引用透明的代码
  • 好的,这只是正常的优势。我确保没有一些微妙和特定于我不知道的异常。谢谢!

标签: c++ exception object-slicing


【解决方案1】:

当您抛出一个对象时,您实际上是在抛出该对象的副本,而不是原始对象。想想看 - 原始对象在堆栈上,但堆栈正在展开并失效。

我相信这是标准的一部分,但我没有可供参考的副本。

在 catch 块中抛出的异常类型是 catch 的基本类型,而不是抛出的对象的类型。解决此问题的方法是使用throw; 而不是throw e;,这将引发原始捕获的异常。

【讨论】:

  • 这表明我不能抛出没有复制构造函数的东西,那么呢?这是有道理的。
【解决方案2】:

A quick google 建议是的,您正在抛出复制构造函数是必需的并且必须是公共的。 (这是有道理的,因为您正在初始化 e 的副本并扔掉它。)

无论如何,只需使用throw 而不指定异常对象,即可重新抛出catch 中捕获的内容。这不应该解决问题吗?

  catch(Exception& e)
  {
    printf("Exception seen here! %s %d\n", __FILE__, __LINE__);
    throw;
  }

【讨论】:

  • 对不起,是的,我已经通过正确地重新抛出来修复了这个错误。我试图确认复制构造函数。
【解决方案3】:

是的。

throw e;

抛出estatic 类型的异常,不管e 实际上是什么。在这种情况下,Derived 异常会使用复制构造函数复制到 Exception

在这种情况下,你可以

throw;

Derived 异常冒泡正确。

如果您对其他情况下的多态抛出感兴趣,请参阅always so useful C++ FAQ Lite

【讨论】:

    【解决方案4】:

    C++ 从未停止让我惊叹。如果这是对行为的赌注,我会损失很多钱!

    异常对象首先被复制到一个临时对象,您应该使用throw。引用标准 15.1/3:

    throw 表达式初始化一个临时对象,称为异常对象,其类型通过从 throw 操作数的静态类型中删除任何顶级 cv 限定符并从“T 的数组”中调整类型来确定"或"函数返回T"分别指向"指向T的指针"或"指向返回T的函数的指针"。

    我认为这产生了一个非常有用的编码标准规则:

    异常层次结构的基类应该有一个纯虚析构函数。

    应保护异常层次结构中基类的复制构造函数。

    两者都实现了编译器在您尝试“抛出 e”时发出警告的目标,因为在第一种情况下您无法创建抽象类的实例,而在第二种情况下,您无法调用复制构造函数。

    【讨论】:

      猜你喜欢
      • 2022-01-09
      • 1970-01-01
      • 1970-01-01
      • 2013-05-02
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2018-06-19
      • 1970-01-01
      相关资源
      最近更新 更多