避免切片异常类型(C ++)

时间:2009-12-06 16:08:39

标签: c++ exception gcc derived object-slicing

我正在为我的库设计C ++的异常层次结构。 “层次结构”是从std :: runtime_error派生的4个类。我想避免异常类的slicing problem,因此使复制构造函数受到保护。但显然gcc需要在抛出它们的实例时调用复制构造函数,因此抱怨受保护的复制构造函数。 Visual C ++ 8.0编译相同的代码。是否有任何可移植的方法来解决异常类的切片问题?标准是否说明实现是否可以/应该要求抛出要抛出的类的复制构造函数?

5 个答案:

答案 0 :(得分:15)

您的异常需要有一个公共拷贝构造函数。编译器必须能够将其复制以进行异常处理才能工作。

问题的解决方案是始终通过引用来捕获:

try {
    // some code...
    throw MyException("lp0 is on fire!");
} catch (MyException const &ex) {
    // handle exception
}

const - ness是可选的,但我总是把它放进去,因为很少需要修改异常对象。)

答案 1 :(得分:9)

托马斯的回答是正确的,但我也建议你不要浪费你的时间来“设计一个异常层次结构”。设计类层次结构是一个特别糟糕的想法,特别是当您可以从C ++标准异常类中简单地派生出一对(并且不多于)新的异常类型时。

答案 2 :(得分:6)

我会避免设计与您的库不同的异常层次结构。尽可能使用std::exception层次结构,始终从该层次结构中的某些内容中获取异常。您可能需要阅读exceptions portion of Marshall Cline's C++ FAQ - 特别是阅读FAQ 17.617.917.1017.12

至于“强迫用户通过引用捕获”,我不知道这样做的好方法。我在一小时左右的比赛中出现的唯一方式(周日下午)基于polymorphic throwing

class foo_exception {
public:
    explicit foo_exception(std::string msg_): m_msg(msg_) {}
    virtual ~foo_exception() {}
    virtual void raise() { throw *this; }
    virtual std::string const& msg() const { return m_msg; }
protected:
    foo_exception(foo_exception const& other): m_msg(other.m_msg) {}
private:
    std::string m_msg;
};

class bar_exception: public foo_exception {
public:
    explicit bar_exception(std::string msg_):
        foo_exception(msg_), m_error_number(errno) {}
    virtual void raise() { throw *this; }
    int error_number() const { return m_error_number; }
protected:
    bar_exception(bar_exception const& other):
        foo_exception(other), m_error_number(other.m_error_number) {}
private:
    int m_error_number;
};

这样做的目的是使复制构造函数受到保护,并强制用户调用Class(args).raise()而不是throw Class(args)。这使您可以抛出一个多态绑定的异常,您的用户只能通过引用捕获它。任何按值捕获的尝试都应该通过一个很好的编译器警告来接受。类似的东西:

  

foo.cpp:59:错误:'bar_exception :: bar_exception(const bar_exception&)'受保护

     

foo.cpp:103:错误:在此上下文中

当然这一切都是有代价的,因为你不能再明确地使用throw,否则你会遇到类似的编译警告:

  

foo.cpp:在函数'void h()'中:

     

foo.cpp:31:错误:'foo_exception :: foo_exception(const foo_exception&)'受保护

     

foo.cpp:93:错误:在此上下文中

     

foo.cpp:31:错误:'foo_exception :: foo_exception(const foo_exception&)'受保护

     

foo.cpp:93:错误:在此上下文中

总的来说,我会依赖编码标准和文档,说明你应该总是通过引用来捕获。确保您的库捕获它通过引用处理的异常并抛出新对象(例如throw Class(constructorArgs)throw;)。我希望其他C ++程序员具有相同的知识 - 但为了确保在任何文档中添加注释。

答案 3 :(得分:0)

我发现阻止我的库客户端通过值错误地捕获异常的两种可移植方式是

  1. 从异常类的virtual raise方法内部抛出异常,并使复制构造函数受到保护。 (感谢D.Shawley)
  2. 从库中抛出派生的异常,并发布要捕获的客户端的异常基类。基类可以有受保护的拷贝构造函数,它只允许捕获它们的好方法。 (提及here提出类似问题)
  3. C ++标准确实声明复制构造函数需要在throw点可访问。我的配置中的Visual C ++ 8.0违反了标准的这一部分,没有强制执行复制构造函数。在第15.1.3节中:

      

    throw-expression初始化一个临时对象,其类型是通过从throw的操作数的静态类型中删除任何顶级cv限定符并从“T数组”或“函数返回”调整类型来确定的。 T“到”指向T“或”指向函数返回T的指针“。

         

    如果可以在不改变程序含义的情况下消除临时对象的使用,除了执行与使用临时对象(12.2)相关的构造函数和析构函数,那么处理程序中的异常可以直接初始化使用throw表达式的参数。当抛出的对象是类对象,并且无法访问用于初始化临时副本的复制构造函数时,程序格式不正确(即使临时对象可能被删除)

    这个答案由OP发布到问题中,我将其从问题中删除并作为单独的答案发布。

答案 4 :(得分:-3)

我要说不要使用任何内置的C ++异常代码。如果您必须有例外,请从头开始创建自己的例外。这是确保它们表现相似的唯一方法,更不用说以类似的方式实现了,而且直截了当地说C ++中的异常实现是无能的。