3

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]

Niall
  • 30,036
  • 10
  • 99
  • 142
Martin
  • 911
  • 7
  • 21
  • [Cannot duplicate](http://ideone.com/SefJCF) – PaulMcKenzie Nov 15 '16 at 04:30
  • That's interesting. It's not valid C++. What compiler are you using? – Cinder Biscuits Nov 15 '16 at 04:33
  • Oh never mind, I didn't realize that was a link. That tool must be optimizing out the bad class since it's unused in your program. – Cinder Biscuits Nov 15 '16 at 04:35
  • somewhat related: http://stackoverflow.com/questions/5874255/is-virtual-inheritance-necessary-for-exceptions – kmdreko Nov 15 '16 at 04:45
  • @PaulMcKenzie oh, sorry, I messed up, apologies, see the revised text – Martin Nov 15 '16 at 07:10
  • 1
    Virtual inheritance imposes a cost, regardless of whether it's used. As a policy, the C++ standard library strives to reject *anything* that fits that description. – Drew Dormann Nov 19 '16 at 20:28
  • @DrewDormann Can you please add a bit of detail about the cost of virtual inheritance in case you're not using it? And I've noticed you said the standard library "strives" to reject such things, and not that virtual inheritance was rejected due to this reason, do you know if the authors of the standard library considered this and accepted the limitation or never considered it? Namely the inability to extend the exception hierarchy without jumping through hoops. – Martin Nov 19 '16 at 22:47
  • @Martin, you may be best served buy posting one or more new, clear, and very specific questions. You have responded to most answers here with more questions. This would be fine in an online chat room, but [is not in the spirit or intention of stackoverflow](http://meta.stackexchange.com/questions/39223) – Drew Dormann Nov 20 '16 at 16:11
  • @DrewDormann something like this? http://stackoverflow.com/questions/40706705/why-does-the-c-standard-library-not-use-virtual-inheritance-for-exceptions – Martin Nov 20 '16 at 16:54

4 Answers4

2

[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.

Community
  • 1
  • 1
Niall
  • 30,036
  • 10
  • 99
  • 142
  • I'm not sure what precisely you mean by "Simply, multiple inheritance, in the exception hierarchy, wasn't intended to be supported. It is not virtually derived, and this is, in effect, what it means." does that mean the exception class hierarchy was not intended to be extended, or it wasn't considered, or something else? .... The question just morphs to "Why doesn't the standard support multiple inheritance?" ... – Martin Nov 26 '16 at 18:25
  • It was intended to be extended, as evidenced in the `virtual` functions, but not to allow a diamond type pattern, as evidenced in the lack of virtual inheritance. I would not think the code is absent by accident, the IO streams library includes virtual inheritance. It may have been an oversight, but I doubt that. On way not, could also be asked as why support it? Lack of use case(s), at the very least lack of general use cases. Virtual inheritance solves a specific problem, not a general problem; general advice is to avoid virtual inheritance to begin with. – Niall Nov 26 '16 at 18:33
  • "Virtual inheritance solves a specific problem", I wonder if that's true ([boost exception recommendations](http://www.boost.org/doc/libs/1_31_0/more/error_handling.html)), apparently boost has reason to recommend virtual inheritance, citing work by Andrew Koenig, I have an example in the question. ... But, my real problem is that you sound unsure ("may have been", "would not think") and it also seems you're saying "it must be for a reason", which, while I appreciate your effort, does not answer the question. I, like the ignorant child I am, must keep asking why, until I learn. Sorry :-) – Martin Nov 26 '16 at 19:09
  • The article credits Andrew for the advice, but doesn't cite the source, hence I fear context for that advice is lost. Certainly the boost exception hierarchy requires and allows for virtual inheritance. Note the examples root the hierarchy to the `std::exception` as a base class (noted in the answer). – Niall Nov 26 '16 at 19:53
  • The point is, the standard library hierarchy is not one of those that requires or allows for virtual inheritance - as evidenced in the code. Maybe a committee member from circa C++98, if they remember, would have a better answer. The same applies for the original author of the boost document you cite, failing the citation from Andrew, the question remains, why did boost choose to do that. – Niall Nov 26 '16 at 19:54
  • What we do have evidence of is the effect of this decision in what code looks like and what does that allow for in your code. We also have evidence of other decisions that have been made for which we have better or more evidence to work with. What is known is that the committee has made mistakes, but decisions are not made lightly and based on other evidence, it stands to reason that the virtual inheritance would have been considered. – Niall Nov 26 '16 at 19:54
  • You're of course right in all you say. However I'm still no closer to an answer to my question. I never assumed the decision would have been made lightly, if I thought so I might not be asking, but since I expect a level of deliberation, I expect a reason, and that is precisely what I'm asking, why is this apparent flaw there. It could be a mistake, but if it's not, it would be of value to know why such a decision was made. Especially, since so far, nobody gave a good answer, if there is a good one, it must be a gem. – Martin Nov 26 '16 at 20:01
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) { }
};
Cinder Biscuits
  • 4,880
  • 31
  • 51
  • 1
    I think you've missed the point. I have other exception classes some of which are logic_error others runtime_error, so my base class needs to be std::exception. – Martin Nov 15 '16 at 06:03
1

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.

n. m. could be an AI
  • 112,515
  • 14
  • 128
  • 243
  • how would this work for something like this exception class hierarchy, sql_exception (std::exception) and sql_disconnected (sql_exception, std::exception, std::runtime_error) and sql_invalid_input (sql_exception, std::exception, std::logic_error). .... I'm not sure I agree with the use of templates in this case since E is always the same, but you intrigue me and gave me a different idea, but first please explain a bit more of what you mean. – Martin Nov 15 '16 at 08:24
  • OK I see what you are trying to do. A bit more complicated thn I initially understood, but not impossible.I will update the answer. – n. m. could be an AI Nov 15 '16 at 08:48
  • yup, what I thought it might be, it's practicable, but I personally don't like the fact that you throw sql_disconnect and have to catch abstract_sql_exception, but I guess that's just the price of being able to do this. You should point this out in the answer, IMHO, for completeness' sake. Do you have any ideas on how to avoid having to catch abstract_sql_exception when throwing sql_disconnected? ... You've answered Q2, thanks, do you have any ideas on Q1, why the standard didn't implement virtual inheritance? – Martin Nov 15 '16 at 22:47
  • 1
    "Why does the standard do this and not that", this question doesn't usually have a good answer. "For historical reasons". – n. m. could be an AI Nov 16 '16 at 05:16
  • I disagree, "historical reasons" is not really an answer. In my experience even mistakes are made for specific reasons, sometimes the reason is ignorance or lack of forethought. Of course, if by good answer you mean a reason we'd agree with, you're absolutely right. But I think the question has an answer, some answer, even if it amounts to the standard is shoddy because nobody gave it enough thought. I'm hoping it's something technical, like a limitation of virtual inheritance I'm not aware of, and by getting my answer I hope to understand more about C++. – Martin Nov 16 '16 at 05:40
0

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.

kmdreko
  • 42,554
  • 6
  • 57
  • 106
  • std::exception already has a virtual destructor, thus a vtable, thus all the dynamic type stuff already applies, how would virtual inheritance make it worse? Am I missing something? – Martin Nov 15 '16 at 06:11
  • 1
    adding virtual inheritance basically adds another level of indirection when accessing a base's members – kmdreko Nov 15 '16 at 07:01
  • oh, yeah, but would you say this would be significant? I mean considering method calls are already indirect and you have to unwind the stack too. And this prevents me from having a class sql_exception (std::exception) and sql_disconnected (sql_exception, std::exception, std::runtime_error) and sql_invalid_input (sql_exception, std::exception, std::logic_error). So I wonder why the standard makers decided to have that limitation when they've already accepted the cost of method indirection. And I wonder how to do what I need, in a "civilized" manner preferably. – Martin Nov 15 '16 at 07:19
  • I never said it was significant, I actually said the opposite. But, your argument follows the same vein as "why don't std containers have virtual destructors?". Most parts of the standard were conceived under the bare-bones, no-fluff, most-efficient idea. I don't believe any part of the standard library uses virtual inheritance so why should they do so here? Rewording from my answer, you should rethink your exception hierarchies seeing as the diamond pattern isn't feasible, no matter how much you think it should be. – kmdreko Nov 15 '16 at 07:31
  • 1
    Thank you, I understand what you mean, and you're right, it's not there, so I can't use it, from a practical point of view your "because the standard ..." is the correct answer. But I'm wondering if it was not considered at all or there was a debate and there was an actual reason this was left out. In any case, thanks. – Martin Nov 15 '16 at 07:39
  • 2
    @Martin - I have never seen any debate about this, and believe that the exceptions in the standard was just seen as an *example* of how to build an exception hierarchy, not as *the* general purpose tool for every need. – Bo Persson Nov 15 '16 at 07:47
  • @BoPersson sorry, I'm confused, shocked actually, why would they put an bunch of examples INTO the standard, to me that seems like an incredibly stupid thing to do. Once it's in the standard, it's there for good, or bad. – Martin Nov 15 '16 at 07:58
  • Having ambiguous inheritance in your exception class is dangerous. If you ever want to catch std::exception and there are two of those in the thrown exception, you get std::terminate. – n. m. could be an AI Nov 15 '16 at 08:09
  • @n.m. which is why people recommend virtual inheritance, so there's only one std::exception, and which is why I wonder for what actual reason the standard prevents multiple virtual inheritance and thus seems to prevent some use cases which are IMHO valuable. – Martin Nov 15 '16 at 08:29
  • Who are these people who recommend virtual inheritance? I've honestly never come across anything but recommendations to avoid it. – Useless Nov 22 '16 at 22:28