为什么std::logic_error不是实际上继承自std::exception
Why does std::logic_error not virtually inherit from std::exception?
我正在尝试实现自定义异常层次结构,并允许适当的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_error
和std::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
中的共享功能而追求这个模型,您可能会选择后者。
- 使用std::multimap迭代器创建std::list
- C++中std::resize(n)和std::shrink_to_fit之间的区别
- 来自 std::list 的迭代器 .end() 按预期返回"0xcdcdcdcdcdcdcdcd"但 .begin()
- C++17复制构造函数,在std::unordereded_map上进行深度复制
- 如何导出包含具有"std::unique_ptr"值的"std::map"属性的
- 从持续时间构造std::chrono::system_clock::time_point
- std::具有相同基类的类的变体
- std::向量与传递值的动态数组
- 使用std::vector的OpenCL矩阵乘法
- std::map<struct,struct>::find 找不到匹配项,但是如果我循环通过 begin() 到 end(),我在那里看到匹配项
- std::condition_variable::wait()如何评估给定的谓词
- 如何获取std::result_of函数的返回类型
- std::原子加载和存储都需要吗
- 将对象移动到std::shared_ptr
- 使用带有 std::cout 的单引号打印字符串实际上打印了数字
- 实际上,C++11 中 std::atomic 的内存占用量是多少?
- 假设 sizeof(std::unordered_map<std::string, T>) 对于所有 T 都相同,实际上是安全的?
- 为什么 std::remove_copy_if() 实际上没有删除?
- 有没有办法检查std::random_device是否实际上是随机的
- 为什么std::logic_error不是实际上继承自std::exception