Are so many subclasses such a good idea?

Discussion in 'C++' started by khapi@yahoo.com, Jun 12, 2008.

  1. Guest

    Hi folks,

    I noticed that the standard library has a bunch of
    subclasses like std::bad_alloc, std::runtime_error
    that derive from std::exception.

    I gather that in order to use one of these error
    subclasses, one creates an instance of that subclass,
    using whichever one is appropriate.

    But is this really a good design practice?
    It seems odd that the creators of std chose subclasses
    for different situations. Why didn't they create
    methods for each situation instead?

    Thanks.
    , Jun 12, 2008
    #1
    1. Advertising

  2. Hi

    schrieb:
    > I noticed that the standard library has a bunch of
    > subclasses like std::bad_alloc, std::runtime_error
    > that derive from std::exception.

    [...]
    > But is this really a good design practice?
    > It seems odd that the creators of std chose subclasses
    > for different situations. Why didn't they create
    > methods for each situation instead?


    ??? - how do you usually throw a /method/ ?

    And if you want to catch these errors selectively, there is no other
    clean way.
    Furthermore that many types don't harm. So where is the problem?


    Marcel
    Marcel Müller, Jun 12, 2008
    #2
    1. Advertising

  3. Guest

    On Jun 12, 4:56 pm, Marcel Müller <>
    wrote:

    > ??? - how do you usually throw a /method/ ?


    I meant not so much to throw a method, but to throw a value
    that in the catch section is handled but just one class
    that handles exceptions, or better yet just one function.

    > And if you want to catch these errors selectively, there is no other
    > clean way.


    Much cleaner:

    enum {ERRORCODE_UNHAPPY, ...};
    try {
    if (whatever())
    throw ERRORCODE_UNHAPPY;
    }
    catch (int e) {
    error_processing(e);
    }

    > Furthermore that many types don't harm. So where is the problem?


    Having too many subclasses obscures the meaning of
    what's really going on. I've seen C++ code that have
    3 or 4 subclasses deep. It's hard to guess what
    the deeper classes really do.
    , Jun 12, 2008
    #3
  4. Kai-Uwe Bux Guest

    wrote:

    > On Jun 12, 4:56 pm, Marcel Müller <>
    > wrote:
    >
    >> ??? - how do you usually throw a /method/ ?

    >
    > I meant not so much to throw a method, but to throw a value
    > that in the catch section is handled but just one class
    > that handles exceptions, or better yet just one function.
    >
    >> And if you want to catch these errors selectively, there is no other
    >> clean way.

    >
    > Much cleaner:
    >
    > enum {ERRORCODE_UNHAPPY, ...};
    > try {
    > if (whatever())
    > throw ERRORCODE_UNHAPPY;
    > }
    > catch (int e) {
    > error_processing(e);
    > }


    And where is the _selective_ catching of errors?


    Think about

    void f ( void ) {
    try {
    // some code that might throw
    }
    catch ( some_exception const & e ) {
    }
    }

    void g ( void ) {
    try {
    f();
    }
    catch ( some_other_exception const & e ) {
    ...
    }
    }

    The point is: different exceptions can rise up in the call stack to
    different levels. The type determines which catch handler is used.


    >> Furthermore that many types don't harm. So where is the problem?

    >
    > Having too many subclasses obscures the meaning of
    > what's really going on. I've seen C++ code that have
    > 3 or 4 subclasses deep. It's hard to guess what
    > the deeper classes really do.


    That seems to be more a problem of documentation and the naming scheme
    employed rather than the depth of the derivation. Besides, with standard
    exception classes, they all do the same (provide a message by means of the
    what() method). The type system is just used to classify errors and allow
    to catch exceptions at the right level in the call stack.


    Best

    Kai-Uwe Bux
    Kai-Uwe Bux, Jun 12, 2008
    #4
  5. Guest

    On Jun 12, 5:49 pm, Kai-Uwe Bux <> wrote:

    > And where is the _selective_ catching of errors?


    Here:
    try {
    if (a)
    throw(UNHAPPY);
    if (b)
    throw(UNHAPPIER);
    if (c)
    throw(UNHAPPIEST);
    } catch (int which) {
    switch (which) {
    case UNHAPPY: ... break;
    case UNHAPPIER: ... break;
    case UNHAPPIEST: ... break;
    }
    }

    > The point is: different exceptions can rise up in the call stack to
    > different levels. The type determines which catch handler is used.


    True, that reinforces C++'s compile-time typing,
    which some consider to be an advantage of C++.
    , Jun 12, 2008
    #5
  6. Noah Roberts Guest

    wrote:
    > On Jun 12, 5:49 pm, Kai-Uwe Bux <> wrote:
    >
    >> And where is the _selective_ catching of errors?

    >
    > Here:
    > try {
    > if (a)
    > throw(UNHAPPY);
    > if (b)
    > throw(UNHAPPIER);
    > if (c)
    > throw(UNHAPPIEST);
    > } catch (int which) {
    > switch (which) {
    > case UNHAPPY: ... break;
    > case UNHAPPIER: ... break;
    > case UNHAPPIEST: ... break;
    > }
    > }


    Because that's a LOT cleaner.
    Noah Roberts, Jun 13, 2008
    #6
  7. red floyd Guest

    wrote:
    > Hi folks,
    >
    > I noticed that the standard library has a bunch of
    > subclasses like std::bad_alloc, std::runtime_error
    > that derive from std::exception.
    >
    > I gather that in order to use one of these error
    > subclasses, one creates an instance of that subclass,
    > using whichever one is appropriate.
    >
    > But is this really a good design practice?
    > It seems odd that the creators of std chose subclasses
    > for different situations. Why didn't they create
    > methods for each situation instead?
    >


    Because that's not extensible. What if I want a new exception type

    class my_exception : std::public exception
    {
    // internals redacted
    };

    How would you provide that in your scheme of things?
    red floyd, Jun 13, 2008
    #7
  8. "Daniel T." <> writes:

    > wrote:
    >> On Jun 12, 5:49 pm, Kai-Uwe Bux <> wrote:
    >>
    >> > And where is the _selective_ catching of errors?

    >>
    >> Here:
    >> try {
    >> if (a)
    >> throw(UNHAPPY);
    >> if (b)
    >> throw(UNHAPPIER);
    >> if (c)
    >> throw(UNHAPPIEST);
    >> } catch (int which) {
    >> switch (which) {
    >> case UNHAPPY: ... break;
    >> case UNHAPPIER: ... break;
    >> case UNHAPPIEST: ... break;
    >> }
    >> }

    >
    > From the above, it looks like you haven't quite gotten the hang of what
    > polymorphism is for. Polymorphism allows one to remove all those
    > duplicated switch statements.


    It was a counter-example!

    The reason why it's good to have a lot of subclasses is indeed to use
    polymorphism.

    try{
    doSomething();
    }catch(UnhappinesDegree e){
    e->recover();
    }


    --
    __Pascal Bourguignon__
    Pascal J. Bourguignon, Jun 13, 2008
    #8
  9. anon Guest

    Chris Thomasson wrote:

    [...]

    >
    > The C++ exception handling mechanism is very expressive. I personally
    > like it a lot. Here is another example. Take a mutex class which can
    > throw exceptions. Here is one way to write it:
    > ___________________________________________________________________
    > class mutex_with_exceptions {
    > pthread_mutex_t m_mtx;
    >
    > [...];
    >
    > public:
    > struct error {
    > struct base {};
    >
    > struct lock {
    > struct base : public error::base {};
    > struct invalid : public lock::base {};
    > struct priority_violation : pubilc lock::invalid {};
    > struct deadlock : public lock::base {};
    > struct max_recursion : public lock::base {};
    >
    > static void raise_status(int const status) {
    > switch (status) {
    > case EINVAL:
    > throw priority_violation();
    > case EAGAIN:
    > throw max_recursion();
    > case EDEADLK:
    > throw deadlock();
    > default:
    > assert(false);
    > std::unexpected();
    > }
    > }
    > };
    >
    > struct unlock {
    > struct base : public error::base {};
    > struct not_owner : public unlock::base {};
    >
    > static void raise_status(int const status) {
    > switch (status) {
    > case EPERM:
    > throw not_owner();
    > default:
    > assert(false);
    > std::unexpected();
    > }
    > }
    > };
    > };
    >
    > public:
    > void lock() {
    > int const status = pthread_mutex_lock(&m_mtx);
    > if (status) {
    > error::lock::raise_status(status);
    > }
    > }
    >
    > void unlock() {
    > int const status = pthread_mutex_unlock(&m_mtx);
    > if (status) {
    > error::unlock::raise_status(status);
    > }
    > }
    > };


    I must say: VERY NICE :)

    > ___________________________________________________________________
    >
    >
    >
    > The exception hierarchy for the `mutex_with_exceptions' class is verbose.
    >
    >
    > Any thoughts?


    I like this explanation. Just small remark - your base class doesn't
    inherit from std::exception
    anon, Jun 13, 2008
    #9
  10. On Jun 12, 10:51 pm, wrote:
    > Hi folks,
    >
    > I noticed that the standard library has a bunch of
    > subclasses like std::bad_alloc, std::runtime_error
    > that derive from std::exception.
    >
    > I gather that in order to use one of these error
    > subclasses, one creates an instance of that subclass,
    > using whichever one is appropriate.
    >
    > But is this really a good design practice?
    > It seems odd that the creators of std chose subclasses
    > for different situations. Why didn't they create
    > methods for each situation instead?
    >
    > Thanks.


    You should differentiate between exception-classes and 'normal'
    classes when looking at the inheritance trees.
    For 'normal' classes, it is usually a bit suspect to have a very deep
    inheritance tree (although there will probably be exceptions).
    For exception-classes, the hierarchy also gives an indication how the
    different exception-classes are related to each other and a deep
    inheritance tree often works good.

    For example, you have the following errors that can be reported: index-
    out-of-range, out-of-memory, io-error.
    The io-error classification can be further divided in network-error
    and file-error.
    The network-error classification can be divided into host-not-found,
    link-lost and connection-refused.
    The file-error classification can be divided into file-not-existing
    and file-not-accessible.
    If you model this as a class hierarchy, it is immediately clear that
    file-not-existing and file-not-accessible belong to the bigger
    category of file-errors. And if you chose to handle all io-errors
    identically at some level, you don't even have to know that there are
    more specific types below that level, nor do you have to change
    anything when someone adds a write-error to the category of file-
    errors.

    Try doing that with numeric error codes.

    Bart v Ingen Schenau
    Bart van Ingen Schenau, Jun 13, 2008
    #10
  11. On 2008-06-13 00:26, wrote:
    > On Jun 12, 5:49 pm, Kai-Uwe Bux <> wrote:
    >
    >> And where is the _selective_ catching of errors?

    >
    > Here:
    > try {
    > if (a)
    > throw(UNHAPPY);
    > if (b)
    > throw(UNHAPPIER);
    > if (c)
    > throw(UNHAPPIEST);
    > } catch (int which) {
    > switch (which) {
    > case UNHAPPY: ... break;
    > case UNHAPPIER: ... break;
    > case UNHAPPIEST: ... break;
    > }
    > }


    What if not all errors can be handled at the same place:

    class e1;
    class e2;
    class e3;

    void f()
    {
    try {
    if (a) throw e1;
    if (b) throw e2;
    if (c) throw e3;
    }
    catch (e1) {
    // ...
    }
    }

    void g()
    {
    try {
    f();
    }
    catch (e2) {
    // ....
    }
    }

    int main()
    {
    try {
    g();
    }
    catch (e3) {
    // ...
    }
    }

    Can not be solved with an int unless you rethrow those that can not be
    handled.

    --
    Erik Wikström
    Erik Wikström, Jun 13, 2008
    #11
  12. schrieb:
    > On Jun 12, 5:49 pm, Kai-Uwe Bux <> wrote:
    >
    >> And where is the _selective_ catching of errors?

    >
    > Here:
    > try {
    > if (a)
    > throw(UNHAPPY);
    > if (b)
    > throw(UNHAPPIER);
    > if (c)
    > throw(UNHAPPIEST);
    > } catch (int which) {
    > switch (which) {
    > case UNHAPPY: ... break;
    > case UNHAPPIER: ... break;
    > case UNHAPPIEST: ... break;
    > }
    > }


    How to boundle additional information with the exception, like the
    filename of the file that couldn't be opened, or the IP-Address of the
    connection attempt that didn't worked out?

    Using a class as exception object, you can attach valuable information
    to the exception.

    >> The point is: different exceptions can rise up in the call stack to
    >> different levels. The type determines which catch handler is used.

    >
    > True, that reinforces C++'s compile-time typing,
    > which some consider to be an advantage of C++.


    And C++'s run-time type information. You don't know until run-time,
    which type of exception will be thrown.

    --
    Thomas
    Thomas J. Gritzan, Jun 13, 2008
    #12
  13. Guest


    > What if not all errors can be handled at the same place:

    ....
    >
    > Can not be solved with an int unless you rethrow those that can not be
    > handled.


    Why rethrow? One can just create a centralized
    handler function.

    void central_handler(int e)
    {
    ...
    }
    void func() {
    try {
    something();
    }
    catch (int e) {
    central_handler(e);
    }
    }
    void func2() {
    try {
    something2();
    }
    catch (int e) {
    central_handler(e);
    }
    }

    And for functions that require immediate
    action that can be done in the catch.
    , Jun 22, 2008
    #13
  14. Guest

    On Jun 13, 9:15 am, Bart van Ingen Schenau
    <> wrote:
    > And if you chose to handle all io-errors
    > identically at some level, you don't even have to know that there are
    > more specific types below that level, nor do you have to change
    > anything when someone adds a write-error to the category of file-
    > errors.
    >
    > Try doing that with numeric error codes.


    OK, I agree that hierarchies of exception classes
    could be useful. I was using that as a concrete example
    however and my complaint was intended more generally
    i.e. deep class inheritance trees are used for
    more than just exceptions.
    , Jun 22, 2008
    #14
  15. peter koch Guest

    On 22 Jun., 15:22, wrote:
    > > What if not all errors can be handled at the same place:

    > ...
    >
    > > Can not be solved with an int unless you rethrow those that can not be
    > > handled.

    >
    > Why rethrow? One can just create a centralized
    > handler function.
    >
    > void central_handler(int e)
    > {
    >  ...}
    >
    > void func() {
    >   try {
    >      something();
    >   }
    >   catch (int e) {
    >      central_handler(e);
    >   }}
    >
    > void func2() {
    >   try {
    >      something2();
    >   }
    >   catch (int e) {
    >      central_handler(e);
    >   }
    >
    > }
    >
    > And for functions that require immediate
    > action that can be done in the catch.


    But that central error-handler would probably only be central for f. I
    assume that your program is not of the type that every error is
    handled in the same manner, no matter the context. If this is the
    case, your program would be quite unusual.

    /Peter
    peter koch, Jun 22, 2008
    #15
  16. Daniel Pitts Guest

    wrote:
    > On Jun 13, 9:15 am, Bart van Ingen Schenau
    > <> wrote:
    >> And if you chose to handle all io-errors
    >> identically at some level, you don't even have to know that there are
    >> more specific types below that level, nor do you have to change
    >> anything when someone adds a write-error to the category of file-
    >> errors.
    >>
    >> Try doing that with numeric error codes.

    >
    > OK, I agree that hierarchies of exception classes
    > could be useful. I was using that as a concrete example
    > however and my complaint was intended more generally
    > i.e. deep class inheritance trees are used for
    > more than just exceptions.
    >
    >
    >


    In general, on way that subclassing is useful in that it provides
    polymorphic behavior. It should almost always be preferred to create a
    new Type, than a Type-Code.

    Warning: Generalization ahead, take with a grain of salt...

    Any time one has a value that is constant for the lifetime of an object
    and affects the behavior of an object, one should attempt to convert
    that into a polymorphic object instead. The leads to less
    "if/then/else" and "switch/case" statements that become more and more
    unmaintainable over time.

    Just my 2 cents.
    --
    Daniel Pitts' Tech Blog: <http://virtualinfinity.net/wordpress/>
    Daniel Pitts, Jun 22, 2008
    #16
    1. Advertising

Want to reply to this thread or ask your own question?

It takes just 2 minutes to sign up (and it's free!). Just click the sign up button to choose a username and then you can ask your own questions on the forum.
Similar Threads
  1. Edward
    Replies:
    1
    Views:
    309
    =?Utf-8?B?UmF2aW5kcmE=?=
    Jun 10, 2004
  2. GG
    Replies:
    8
    Views:
    292
    Tim Jowers
    Aug 23, 2004
  3. Jacqui
    Replies:
    0
    Views:
    420
    Jacqui
    Jan 4, 2006
  4. Replies:
    10
    Views:
    1,221
    Big K
    Feb 2, 2005
  5. Dr Mephesto

    App idea, Any idea on implementation?

    Dr Mephesto, Feb 4, 2008, in forum: Python
    Replies:
    3
    Views:
    702
    Dennis Lee Bieber
    Feb 5, 2008
Loading...

Share This Page