为什么std::logic_error不是实际上继承自std::exception

Why does std::logic_error not virtually inherit from std::exception?

本文关键字:std 实际上 继承 exception error logic 为什么      更新时间:2023-10-16

我正在尝试实现自定义异常层次结构,并允许适当的std::*被代码捕获。

class my_exception : public virtual std::exception {
};
class my_bad_widget_state : public virtual my_exception, public virtual std::logic_error {
   public: my_bad_widget_state() : std::logic_error("widget oops") {}
};

显然my_bad_widget_state是一个my_exception,也是一个std::logic_error,但是编译器拒绝了这段代码,因为std::exception在继承exception时没有说virtual,所以有歧义。编译器是正确的,但我认为标准库可能是错误的,或者?

编辑:

显然,my_bad_widget_state是一个my_exception,所以是一个logic_error,也是一个std::exception,当my_bad_widget_state被抛出时,std::exception没有被捕获。

编辑:

我很想知道标准库是出于某种特定的原因设计成这种方式的,而我到目前为止还没有理解(如果是这样,请告诉我是什么原因),还是因为某种疏忽。我的研究表明,许多人似乎认为这是一个问题,但我没有找到任何理由认为继承不应该是虚拟的。

Q1:为什么标准库中的继承不是虚的?

Q2:如何正确实现?罢工(回答)

[W]为什么是继承[w.r.t。异常]在标准库中不是虚拟的?

简单地说,在标准异常层次结构中,不打算支持多重继承。它实际上不是派生出来的,这就是它实际上的意思。

相比之下,标准库中哪里支持

?I/O流是我想到的第一个例子。特别是从basic_ios一直到basic_iostream的使用。在这种情况下,基类被虚拟地派生以支持多重继承,并且避免了"菱形问题"。

那么为什么会这样,std::exception应该如何使用呢?

std::exception有来自它的多个例外,特别注意std::logic_errorstd::runtime_error。标准库已经给了我们一个分类和组织异常的板模式,即;

class logic_error;

定义要作为异常抛出的对象类型。它报告由程序中错误逻辑导致的错误,例如违反逻辑前提条件或类不变量,这些错误是可以预防的。

class runtime_error;

定义要作为异常抛出的对象类型。它报告由于超出程序范围的事件而导致的错误,并且不容易预测。

当然,这不是唯一的两个异常,但它们捕获并构成了大量其他标准库异常的基础。

在哪里根异常层次结构?

  • 如果希望使用标准库异常层次结构,最好选择一个扩展层次结构的点,并从该点开始工作。因此,如果希望有一个自定义根异常,那么将std::exception作为基类,并从该自定义基类派生进一步的自定义异常。

  • 如果自定义异常可在运行时错误和逻辑错误之间划分,则从该级别派生自定义异常层次。

使用根植于标准库异常中的自定义异常层次结构通常是个好主意。根应该在什么位置取决于代码的实际预期用途。请参阅此处查看有关此问题的更广泛的问答。

boost异常怎么办?

Boost使用虚拟继承,它们这样做是为了准确地支持标准库不支持的多重继承。它还支持一些标准库中没有的附加功能。

也就是说,boost仍然使用std::exception作为基类。

最终,这将成为基于您希望在层次结构中支持的继承结构的设计决策。

std::logic_error必须声明构造函数才能继承。如果使用c++ 11,可以使用using:

继承基类构造函数。
class MyException : public std::logic_error {
    public:
        using std::logic_error::logic_error;
};

在c++ 0x中,您只需要显式地编写一个构造函数,它接受std::string并将其转发给基类构造函数,如下所示:

class MyException : public std::logic_error {
    public:
        MyException(std::string const& msg) : std::logic_error(msg) { }
};

在具体的层次结构中使用虚拟继承是相当尴尬的,因为您需要在所有后代类(子类、孙子类等)中初始化虚拟基类

如果你想为所有标准异常类添加功能,你可以这样做

class my_exception_additions {
 // no inheritance from std::exception
};
template <class E>
class my_exception : public E,
  public my_exception_additions {
   ...
};
...
throw my_exception<std::logic_error>("oops");

当然模板需要将构造函数转发给e。

现在,如果您想要两个独立的层次结构,如std::exception和注释中的sql_exception,则模板机制变得过于复杂,最好诉诸于手动定义所有类:

class abstract_sql_exception {...};
class sql_exception : public abstract_sql_exception,
                      public std::exception {...};
class abstract_sql_disconnected : public abstract_sql_exception {...};
class sql_disconnected : public abstract_sql_disconnected,
                         public std::runtime_error {...};
class abstract_sql_invalid_input : public abstract_sql_exception {...};
class sql_invalid_input : public abstract_sql_invalid_input,
                          public std::logic_error {...};

这里,abstract_sql层次结构完全独立于std::层次结构存在。只有具体的叶子类将两者联系在一起。

我必须说这是一个(或多或少丑陋的)变通方法,而不是一个理想的解决方案。标准应该在整个异常层次结构中指定虚拟继承。

std::logic_error实际上不继承std::exception,因为标准没有这样说。这样做的原因很可能是基本上不需要表达标准如何使用异常。虚拟继承还增加了复杂性和成本(尽管与异常处理相比微不足道)

你当然可以做你想做的,不继承虚拟,警告你在my_bad_widget_state中有两个基本的std::exception对象。这样做的主要问题是,您无法通过catch (std::exception& e) ...捕获my_bad_widget_state异常,因为到std::exception的转换是不明确的。

我的建议是不要使用虚拟继承,而是坚持使用异常类(logic_error, runtime_error等),或者让所有异常都从my_exception独家继承。如果您因为my_exception中的共享功能而追求这个模型,您可能会选择后者。