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

时光怂恿深爱的人放手 提交于 2019-12-10 23:44:40

问题


I'm trying to implement a custom exception hierarchy and allow appropriate std::* to be caught by code.

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

Obviously my_bad_widget_state is a my_exception and is also a std::logic_error, but the compiler rejects this code because std::exception doesn't say virtual when inheriting exception so there's an ambiguity. The compiler is right, but I think the standard library might be wrong, or?

edit: Obviously my_bad_widget_state is a my_exception so a logic_error and also a std::exception, and when my_bad_widget_state is thrown std::exception is not being caught.

edit: I am interested in knowing whether the standard library is designed this way for a particular reason that I failed to understand so far (if so, what is that reason please) or is it some kind of an oversight. My research indicates that many people seem to think this is a problem, but I didn't find any reason the inheritance shouldn't be virtual.

Q1: why is the inheritance in the standard library not virtual?

Q2: how can this be implemented correctly? [answered]


回答1:


std::logic_error cannot be inherited without declaring a constructor. If you are using C++11, you can inherit the base class constructor by utilizing using:

class MyException : public std::logic_error {
    public:
        using std::logic_error::logic_error;
};

In C++0x, you just have to explicitly write a constructor that takes an std::string and forwards it to the base-class constructor like so:

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



回答2:


Virtual inheritance is rather awkward to use in a concrete hierarchy, because you need to initialize a virtual base in all descendant classes (children, grandchildren, ...)

If you want to add functionality to all standard exception classes, you can do this

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");

Of course the template will need to forward constructors to E.

Now if you want two separate hierarchies, like std::exception and your sql_exception from the comments, the template machinery becomes too complicated and it's better to resort to manually defining all classes:

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

Here, the abstract_sql hierarchy exists completely independently from the std:: hierarchy. Only concrete leaf classes tie the two together.

I must say that this is a (more or less ugly) workaround, not an ideal solution. The standard should have probably specified virtual inheritance throughout the exception hierarchy.




回答3:


[W]hy is the inheritance [w.r.t. exceptions] in the standard library not virtual?

Simply, multiple inheritance, in the standard exception hierarchy, wasn't intended to be supported. It is not virtually derived, and this is, in effect, what it means.

By contrast, where in the standard library is this supported? I/O streams is the first example that comes to mind. In particular the use of basic_ios all the way down the hierarchy to basic_iostream. In this case, it was intended that the base was virtually derived to support the multiple inheritance and that the "diamond problem" was avoided.

So why is this, how should std::exception be used?

std::exception has multiple exceptions that are derived from it, in particular, note the std::logic_error and std::runtime_error. The standard library has already given us a board pattern for classification and organisation of our exceptions, namely;

class logic_error;

Defines a type of object to be thrown as exception. It reports errors that are a consequence of faulty logic within the program such as violating logical preconditions or class invariants and may be preventable.

And

class runtime_error;

Defines a type of object to be thrown as exception. It reports errors that are due to events beyond the scope of the program and can not be easily predicted.

Of course these are not the only two, but they capture and are a base of a significant number of other standard library exceptions.

Where to root the exception hierarchy?

  • If you wish to use the standard library exception hierarchy, it is better to choose a point at which to extend the hierarchy and work from that point on. Hence, if there is a desire to have a custom root exception, then have std::exception as a base class and derive further custom exceptions from that custom base onwards.

  • If the custom exceptions are divisible between runtime and logic errors, then derive the custom exception hierarchy from that level onwards.

Using a custom exception hierarchy rooted somewhere in the standard library exceptions is generally a good idea. At what point that root(s) should be is dependent on the actual intended use of the code. See here for a broader Q&A on this.

What about boost exceptions?

Boost uses virtual inheritance, they do this to exactly support the multiple inheritance that the standard library does not support. It also supports some additional features not found in the standard library.

That said, boost still uses the std::exception as a base class.

Ultimately this becomes a design decision based on the inheritance structures you wish to support in the hierarchy.




回答4:


std::logic_error doesn't inherit virtually from std::exception because the standard doesn't say it does. The reason for that is likely that it is largely unneeded to express how the standard uses exceptions. Virtual inheritance also adds complexity and cost (albeit insignificant compared to exception handling)

You can certainly do what you want by not inheriting virtually with the caveat that you'd have two base std::exception objects in my_bad_widget_state. The primary issue with that is that you then can't catch a my_bad_widget_state exception by catch (std::exception& e) ... because the conversion to std::exception is ambiguous.

My advice is to not to use virtual inheritance and instead either stick to the exception classes (logic_error, runtime_error, etc.) or have all your exceptions inherit exclusively from my_exception. If you are pursuing this model because of shared functionality in my_exception, you'd probably opt for the latter.



来源:https://stackoverflow.com/questions/40601894/why-does-stdlogic-error-not-virtually-inherit-from-stdexception

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!