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

sho*_*tsy 5 c++ gcc exception derived object-slicing

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

Tho*_*mas 15

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

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

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

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

  • 此外:始终抛出新创建的异常(或至少在您知道如何创建异常的情况下).不要通过解除引用的指针或引用(可能是派生类)抛出. (3认同)

小智 9

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

  • +1.我在这里和尼尔在一起.如果您认为您的异常比其他异常发生,因此需要它自己的异常类型(您可能有错误情况而不是例外).否则每个功能单元有1个例外(如何定义功能单元是模糊的但是更大而不是更小). (2认同)

D.S*_*ley 6

我会避免设计与您的库不同的异常层次结构.std::exception尽可能使用层次结构,并始终从该层次结构中的某些内容派生异常.你可能想读马歇尔克莱恩公司的C的异常部分++ FAQ -阅读FAQ 17.6,17.9,17.1017.12尤其如此.

至于"强迫用户引用",我不知道这样做的好方法.我在一个小时左右的比赛中得出的唯一方法(周日下午)基于多态投掷:

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;
};
Run Code Online (Sandbox Code Playgroud)

这个想法是使复制构造函数受到保护并强制用户调用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++程序员具有相同的知识 - 但只是为了确保在任何文档中添加注释.