创建异常层次结构的正确方式是什么?

4
我明白要正确地使用多重继承来捕获异常,需要使用虚拟继承。 我并不一定支持有争议的多重继承使用,但我不想设计使其使用成为不可能的系统。 请不要从这个问题转移话题去支持或攻击多重继承的使用。
假设我有以下基本异常类型:
class BaseException : public virtual std::exception {
public:
    explicit BaseException(std::string msg)
    : msg_storage(std::make_shared<std::string>(std::move(msg))) { }

    virtual const char* what() const noexcept { return msg_storage->c_str(); }

private:
    // shared_ptr to make copy constructor noexcept.
    std::shared_ptr<std::string> msg_storage;
};

如何从BaseException创建一个正确的异常层次结构?
我的问题在于构造异常类型。理想情况下,每个异常只需要构造其父级即可,但由于虚继承的存在,这是不可能的。一种解决方法是构造链中的每个父级:
struct A : public virtual BaseException {
    explicit A(const std::string& msg) : BaseException(msg) { }
};

struct B : public virtual A {
    explicit B(const std::string& msg, int code)
    : BaseException(msg), A(msg), code_(code) { }

    virtual int code() const { return code_; }

private:
    int code_;
};

struct C : public virtual B {
    explicit C(const std::string& msg, int code)
    : BaseException(msg), A(msg), B(msg, code) { }
};

但这似乎非常重复且容易出错。此外,这使得异常类型的构造函数无法在传递到各自的父级之前添加/更改由其子级传递的信息。

1
我想知道这些最终是否真的有用。您是否拥有代码或期望客户端拥有代码,可以捕获更多不仅仅是std::exceptionBaseException的异常,并根据抛出的异常类型采取不同的行动? - Christian Hackl
1
你看过Boost.Exception以及其文档中的讨论吗? - Angew is no longer proud of SO
4
在这种情况下,我认为没有人“打破常规”。尽管您的问题(虽然不是针对异常层次结构,而是普遍继承)在标准化过程中受到了严肃考虑。最终,人们认为由于没有有效的用途来进行带有数据成员的类的虚拟继承,因此没有必要为解决问题而使语言变得更加复杂。 - James Kanze
@orlp:我认为评论是思考创新的正确场所。相比之下,回答应该更加专注于您所谓的“在理论框架内思考”,特别是如果提问者显然知道自己在说什么(正如这个问题的情况)。我可以理解“超越常规”的答案可能会让人感到烦恼,但评论呢? - Christian Hackl
@orlp:我仍然不明白你的使用情况是什么。 - quamrana
显示剩余2条评论
1个回答

2

我找到了一个合理的解决方案,就我所看到的而言并没有出现问题。它使用了一个稍微修改过的基础异常类型:

struct BaseException : virtual std::exception {
    explicit BaseException(std::string msg)
    : msg_storage(std::make_shared<std::string>(std::move(msg))) { }

    virtual const char* what() const noexcept { return msg_storage->c_str(); }

protected:
    BaseException();

private:
    std::shared_ptr<std::string> msg_storage;
};

然后规则如下:
  1. 每个异常公开且虚拟地继承其父异常。

  2. 每个异常都声明了一个受保护的默认构造函数并定义了一个初始化所有数据成员的受保护构造函数。

  3. 每个应该是可构造的异常都定义了一个公共构造函数,该构造函数直接调用2中定义的每个祖先的构造函数。

  4. 所有复制构造函数应该是noexcept的。


使用此示例层次结构:

// A regular derived exception.
struct RuntimeError : virtual BaseException {
    RuntimeError(std::string msg) : BaseException(std::move(msg))  { }
protected: RuntimeError() { }
};

// Derived exception with data member.
struct OSError : virtual RuntimeError {
    OSError(std::string msg, int e) : BaseException(std::move(msg)), e(e)  { }
    virtual int error_code() const noexcept { return e; }

protected:
    OSError();
    OSError(int e) : e(e) { }

private:
    int e;
};

// Non-constructible exception type.
struct FatalError : virtual RuntimeError {
protected: FatalError() { }
};

// A composed exception type.
struct FatalOSError : virtual FatalError, virtual OSError {
    FatalOSError(std::string msg, int e)
    : BaseException(std::move(msg)), OSError(e)  { }
protected: FatalOSError() { }
};

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接