为什么std :: logic_error几乎不从std :: exception继承?

时间:2016-11-15 04:11:45

标签: c++ inheritance std

我试图实现自定义异常层次结构,并允许代码捕获适当的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在继承异常时并没有说虚拟,所以存在歧义。编译器是对的,但我认为标准库可能是错误的,或者?

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

修改 我有兴趣知道标准库是否是按照我迄今为止无法理解的特定原因设计的(如果是这样,请原因是什么)或者是某种疏忽。我的研究表明,很多人似乎认为这是一个问题,但我没有找到任何理由继承不应该是虚拟的。

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

Q2:如何正确实施? [回答]

4 个答案:

答案 0 :(得分:1)

如果不声明构造函数,则无法继承

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

答案 1 :(得分:1)

虚拟继承在具体层次结构中使用相当笨拙,因为您需要在所有后代类(子,孙,......)中初始化虚拟基础

如果要为所有标准异常类添加功能,可以执行此操作

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 :: hierarchy。只有具体的叶子类将两者结合在一起。

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

答案 2 :(得分:1)

  

[W] hy是继承[w.r.t.异常]在标准库中不是虚拟的吗?

简单地说,标准异常层次结构中的多重继承并不是支持的。它实际上并非衍生出来,实际上,这意味着它的含义。

相比之下,支持标准库中的哪个位置? I/O streams是我想到的第一个例子。特别是basic_ios在层次结构中一直使用basic_iostream。在这种情况下,基本上是为了支持多重继承以及钻石问题而设计的。避免了。

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

std::exception有多个例外情况,特别是请注意std::logic_errorstd::runtime_error。标准库已经为我们提供了一个用于分类和组织我们的例外的董事会模式,即

  

class logic_error;

     

定义要作为异常抛出的对象类型。它报告错误是程序中错误逻辑的结果,例如违反逻辑前置条件或类不变量,并且可能是可以预防的。

  

class runtime_error;

     

定义要作为异常抛出的对象类型。它报告的错误是由超出程序范围的事件引起的,并且无法轻易预测。

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

根据异常层次结构的根位置?

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

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

使用以标准库异常为根的自定义异常层次结构通常是一个好主意。在什么时候根应该取决于代码的实际预期用途。 See here for a broader Q&A on this

提升异常怎么办?

Boost uses virtual inheritance,他们这样做是为了完全支持标准库不支持的多重继承。它还支持标准库中没有的一些其他功能。

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

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

答案 3 :(得分:0)

std::logic_error不会从std::exception虚拟继承,因为标准没有说明。其原因很可能是表达标准如何使用异常在很大程度上是不必要的。虚拟继承还增加了复杂性和成本(尽管与异常处理相比无关紧要)

你可以做到你想要的,因为你没有在std::exception中有两个基础my_bad_widget_state对象的警告。与此相关的主要问题是,您无法通过my_bad_widget_state抓取catch (std::exception& e) ...例外,因为转换为std::exception的模糊不清。

我的建议是不要使用虚拟继承,而是坚持异常类(logic_errorruntime_error等)或让所有异常仅从my_exception继承。如果由于my_exception中的共享功能而追求此模型,您可能会选择后者。