代码之家  ›  专栏  ›  技术社区  ›  Michael Kohne

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

  •  10
  • Michael Kohne  · 技术社区  · 15 年前

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

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

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

    这个bug当然是outerCall最终抛出了一个异常,而不是派生的。我的错误是由于调用堆栈试图捕获派生的失败而导致的。

    现在,我只想确保我理解——我相信在“throw e”行,正在使用默认的复制构造函数创建一个新的异常对象。这是真的吗?

    如果是这样,是否允许为将被抛出的对象锁定复制构造函数?我真的不希望这种情况再次发生,而且我们的代码没有理由复制异常对象(我知道)。

    请不要评论我们有自己的异常层次结构这一事实。这是一个我正在努力纠正的旧设计(我正在取得良好的进展)。我已经摆脱了自制的字符串类和许多自制的容器。)

    更新:很明显,在我问这个问题之前,我已经修复了这个错误(把“throw e”改为“throw”)。我只是想确认发生了什么事。

    4 回复  |  直到 10 年前
        1
  •  21
  •   Mark Ransom    15 年前

    当你扔一个物体时,你实际上是扔一个物体的副本,而不是原件。想想看-原始对象在堆栈上,但堆栈正在被解绕和失效。

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

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

        2
  •  10
  •   Stack Overflow is garbage    15 年前

    A quick google 建议是的,您要抛出的复制构造函数是必需的,并且必须是公共的。(这很有意义,因为您正在初始化 e 然后扔出去。)

    不管怎样,只要用 throw 在不指定异常对象的情况下,重新引发 catch . 这难道不能解决问题吗?

      catch(Exception& e)
      {
        printf("Exception seen here! %s %d\n", __FILE__, __LINE__);
        throw;
      }
    
        3
  •  7
  •   laalto    15 年前

    对。

    throw e;
    

    引发的异常 静止的 类型 e 不管怎样 e 事实上是。在这种情况下, Derived 异常被复制到 Exception 使用复制构造函数。

    在这种情况下,你可以

    throw;
    

    得到 衍生的 异常正确冒泡。

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

        4
  •  1
  •   Richard Corden    15 年前

    C++永远不会让我吃惊。如果这是对行为的赌注,我会损失很多钱的!

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

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

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

    异常层次结构的基类应具有纯虚拟析构函数。

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

    这两种方法都达到了当您试图“抛出e”时编译器会发出警告的目标,因为在第一种情况下,您不能创建抽象类的实例,而在第二种情况下,您不能调用复制构造函数。