Thread: Exception handling framework based on multiple inheritance

  1. #1
    (?<!re)tired Mario F.'s Avatar
    Join Date
    May 2006
    Location
    Ireland
    Posts
    8,446

    Exception handling framework based on multiple inheritance

    I've reshaped my exception handling module to something that seems to me is more manageable. But I hit a serious obstacle.

    The new exception handling framework builds on top the standard library exceptions. Two distinct hierarchies make up the framework.

    - The standard exceptions rooted in the std::exception class; nothing new there.
    - And the Mediaeval exception hierarchy rooted in the mediaeval_exception class.

    The purpose of the mediaeval_exception class and its derived classes differs from the standard library in that it attempts to describe exceptions in the context of the application modules and some of the main objects being used (objects that, because of their complexity, deserve this honor).

    Meanwhile because this context doesn't necessarily preclude the logic behind the standard exception hierarchy, at the lowest level of granularity, its classes also derive from either std::runtime_error or std::logic_error where appropriate. I believe this way both contexts are served.


    Follows a snippet of the structure:
    Code:
    #include <stdexcept>
     
    namespace mediaeval {
      namespace error {
     
         class mediaeval_exception { // TOP CLASS
         public:
            explicit mediaeval_exception(const char *err): cstr_(err) {}
     
            virtual ~mediaeval_exception() throw() {}
     
            virtual const char* what() const throw() { return cstr_; }
         private:
            const char * cstr_;
         };
     
         //Base class of a module related branch
         class database_error: public mediaeval_exception {
         public:
            explicit database_error(const char *err)
            : mediaeval_exception(err) {}
     
            virtual ~database_error() throw() {}
         };
     
         //Base class of a Unit object related branch
         class unit_error: public mediaeval_exception {
         public:
            explicit unit_error(const char *err): mediaeval_exception(err) {}
            virtual ~unit_error() throw() {}
         };
     
         // One of the database module related exceptions
         class data_corruption: public database_error, public std::runtime_error {
         public:
            explicit invalid_database(const char *desc)
            : database_error(desc), std::runtime_error(desc) {}
     
            /* MI ambiguity resolution. */
            virtual const char* what() const throw()
            { return mediaeval_exception::what(); }
         };
     
         // One of the Unit object related exceptions
         class invalid_unit: public unit_error, public std::logic_error {
         public:
            explicit invalid_unit(const char *desc)
            : unit_error(desc), std::logic_error(desc) {}
     
            /* MI ambiguity resolution. */
            virtual const char* what() const throw()
            { return mediaeval_exception::what(); }
         };
     
      } // namespace error
    } // namespace mediaeval
    The problem is this:
    Base branch classes (like unit_error and database_error above) can also be thrown. As can (in theory) the TOP mediaeval_exception. This is so because I would rather prefer not to overpopulate the hierarchy. As such some errors, because of their rarity or general nature aren't expressed in terms of a leaf class. For instance, I don't have a self_reference error for Unit objects. If a unit tries to hit itself I throw unit_error("Unit tries to hit self.").

    I wanted to have mediaeval_exception (the TOP class) inherit virtually from std::exception. Only that way I could guarantee that a user catching a std::exception& object would also catch any mediaeval_exception errors.

    You are probably already grinning at this point. And rightly so.

    I can't do that. Sadly, std::logic_error and std::runtime_error don't inherit virtually from std::exception.

    Sometimes I feel this mortally wounds my framework. Other times I think it is clearly defined enough so that a user knows they must not rely solely on std::exception& just because I happen to derive the leaf classes from the standard library.

    What do you think? Should I extend my framework to define all exceptions and make the base classes ABCs? Or is there another way? Or shouldn't I worry with that since the framework speaks for itself? Lastly, should I even be using MI on those leaf classes? Sometimes I also feel this exactly what is encouraging the problem and connecting the two hierarchies isn't really that of an advantage of even desirable.
    Originally Posted by brewbuck:
    Reimplementing a large system in another language to get a 25% performance boost is nonsense. It would be cheaper to just get a computer which is 25% faster.

  2. #2
    The larch
    Join Date
    May 2006
    Posts
    3,573
    ...For instance, I don't have a self_reference error for Unit objects. If a unit tries to hit itself I throw unit_error("Unit tries to hit self.").
    Hmm, do you perhaps use exceptions for program flow control? If a unit is not supposed to hit itself, then just return early if you discover that that is what it is attempting? I mean, if this is how a unit is supposed to work, you'll have the knowledge what to do at the place where the attempt is made...
    I might be wrong.

    Thank you, anon. You sure know how to recognize different types of trees from quite a long way away.
    Quoted more than 1000 times (I hope).

  3. #3
    (?<!re)tired Mario F.'s Avatar
    Join Date
    May 2006
    Location
    Ireland
    Posts
    8,446
    Not sure if I'm following anon.

    The problem can arise, for instance, from a situation like:

    Code:
    Unit A;
    Unit &B = A;
    
    A.hit(B); // this throws
    Naturally, the user should code correctly to not make these things happen. However, I cannot ignore it while implementing the class.
    Originally Posted by brewbuck:
    Reimplementing a large system in another language to get a 25% performance boost is nonsense. It would be cheaper to just get a computer which is 25% faster.

  4. #4
    Lurking whiteflags's Avatar
    Join Date
    Apr 2006
    Location
    United States
    Posts
    9,612
    Err, well.. I am confused apparently. Well, if possible, maybe the assignment operator should be private? You really do need to catch some of these things at compile time.
    Last edited by whiteflags; 06-25-2007 at 08:30 AM.

  5. #5
    (?<!re)tired Mario F.'s Avatar
    Join Date
    May 2006
    Location
    Ireland
    Posts
    8,446
    But there's isn't a issue with base or derived classes here. Both A and B are objects of Unit type (granted one is a reference). The fact I'm making a reference to a Unit object there is simply to illustrate one of the most common errors (I think); The Unit user forgets that B is simply a reference to A, not an object distinct from A. Meanwhile one of the hit() member function preconditions is that This != Unit_argument_passed.

    ... I'm completely lost...

    Do you mean in the exception handling framework I detailed above? Those classes are supposed to be used according to the normal rules of public inheritance. A user can catch by passing a base class reference or a reference to the specific error.

    I just don't see how that relates to what is being said.
    Originally Posted by brewbuck:
    Reimplementing a large system in another language to get a 25% performance boost is nonsense. It would be cheaper to just get a computer which is 25% faster.

  6. #6
    Lurking whiteflags's Avatar
    Join Date
    Apr 2006
    Location
    United States
    Posts
    9,612
    > ... I'm completely lost...

    I'm sorry, I didn't really give this the attention it deserved. My advice though, is to not overuse C++ exceptions. Stuff like units hitting themselves, or items being used out of place, or whatever are things that can be handled by well constructed logic in the program, as anon was saying. You have to distinguish between things that you should throw (because they are events that don't arise during average play - like a database crashing) and what you can control.

    Otherwise you end up with using exceptions for expected errors which is not what exceptions are for, I think. Impossible events should be catered to by your system, and not exceptions.

  7. #7
    (?<!re)tired Mario F.'s Avatar
    Join Date
    May 2006
    Location
    Ireland
    Posts
    8,446
    Ah gotcha!

    Yes. The hierarchy I shown is a lame example. As I go I will certainly be removing from it (and adding too probably) since I will certainy follow your and Anon's advise and try to model my classes so that they can catch at compile time many of the errors.

    This whole application is simply one huge exercise as I learn C++. So I'm not under any stress. If I have to rewrite large portions of the code, I will happily do so; I learned something new. One day, in years to come, it will be done. Or I get bored or find something else more interesting to do and it will never will.

    However, my original question still remains unanswered.
    Originally Posted by brewbuck:
    Reimplementing a large system in another language to get a 25% performance boost is nonsense. It would be cheaper to just get a computer which is 25% faster.

  8. #8
    Registered User
    Join Date
    Sep 2001
    Posts
    752
    I can't do that. Sadly, std::logic_error and std::runtime_error don't inherit virtually from std::exception.
    Last time I said this, I was wrong, but I am pretty sure this is not true. I mean, you're saying this code doesn't catch the exception for you?

    Code:
    #include <exception>
    #include <stdexcept>
    #include <iostream>
    
    int main (void) {
       try {
          throw std::runtime_error ("The buck stops here!\n");
       }
       catch (std::exception & e) {
          std::cout << e.what();
       }
    
       return 0;
    }
    Last edited by QuestionC; 06-25-2007 at 09:10 AM. Reason: Spelling error
    Callou collei we'll code the way
    Of prime numbers and pings!

  9. #9
    (?<!re)tired Mario F.'s Avatar
    Join Date
    May 2006
    Location
    Ireland
    Posts
    8,446
    Oh, that it does. std::runtime_error and std::logic_error derive from std::exception. MinGW implementators would have a lot of explaining to do if it didn't hehe.

    What they don't do is derive virtually. So, any attempt of doing the following inheritance fails:

    Code:
                 [std::exception]
                        /  \
    [mediaeval_exception]   [std::runtime_error]
                        \  /
                 [database_error]
    mediaeval_exception and std::runtime_error would have to derive virtually from std::exception.
    Last edited by Mario F.; 06-25-2007 at 09:21 AM. Reason: You wouldn't believe the trouble I had drawing that.
    Originally Posted by brewbuck:
    Reimplementing a large system in another language to get a 25% performance boost is nonsense. It would be cheaper to just get a computer which is 25% faster.

  10. #10
    The larch
    Join Date
    May 2006
    Posts
    3,573
    Well, the self-hit thing is similar to self-assignment. Why throw, if you can do nothing.

    If however, self-hit is a result of a programming bug, I think you should use assert to catch it. If it should never happen in a release version, why go through the trouble to make an exception for it?

    As I understand it, assert is for debugging programmer errors, exceptions are (mainly) for "unforeseeable" runtime errors (either caused by the user or the user's machine).
    I might be wrong.

    Thank you, anon. You sure know how to recognize different types of trees from quite a long way away.
    Quoted more than 1000 times (I hope).

  11. #11
    (?<!re)tired Mario F.'s Avatar
    Join Date
    May 2006
    Location
    Ireland
    Posts
    8,446
    The circular-reference exception cannot be solved at compile time from all I can see. So we have here an exception. No matter if it is a coding bug or intentional. I can assert (and I should assert, and I do assert). But assert is not the answer to all prayers. If a bug creaps out to the release version, because the test conditions during the debugging process didn't fire the assert, I'm basically left where I was before. With a bug and one that would probably make my application work in some new interesting ways, because... I didn't made an exception of this situation.

    So, I make this an exception. It's not expected to happen. But can happen; because of a coding error, or because of bad user input.

    However, I'm afraid to say, that is not really what is worrying me Trust me, as I go along the smaller that hierarchy I shown on the first post, the merrier I will be.

    I'm more interested in the original question.
    Originally Posted by brewbuck:
    Reimplementing a large system in another language to get a 25% performance boost is nonsense. It would be cheaper to just get a computer which is 25% faster.

  12. #12
    Cat without Hat CornedBee's Avatar
    Join Date
    Apr 2003
    Posts
    8,895
    I suggest using assertions instead of all logic_error derived exceptions, and then deriving your entire hierarchy from runtime_error.
    All the buzzt!
    CornedBee

    "There is not now, nor has there ever been, nor will there ever be, any programming language in which it is the least bit difficult to write bad code."
    - Flon's Law

Popular pages Recent additions subscribe to a feed

Similar Threads

  1. exception handling
    By coletek in forum C++ Programming
    Replies: 2
    Last Post: 01-12-2009, 05:28 PM
  2. exception handling wrapper functions?
    By m37h0d in forum C++ Programming
    Replies: 17
    Last Post: 06-10-2008, 02:56 PM
  3. exception handling, function call and memory
    By George2 in forum C++ Programming
    Replies: 21
    Last Post: 01-30-2008, 08:00 AM
  4. Multiple Inheritance Ambiguity
    By FillYourBrain in forum C++ Programming
    Replies: 21
    Last Post: 08-23-2002, 10:31 AM
  5. Multiple virtual inheritance
    By kitten in forum C++ Programming
    Replies: 3
    Last Post: 08-10-2001, 10:04 PM