Are so many subclasses such a good idea?

K

khapi

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

Marcel Müller

Hi

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
 
K

khapi

??? - 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.
 
K

Kai-Uwe Bux

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.


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.

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
 
K

khapi

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++.
 
N

Noah Roberts

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

red floyd

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?
 
P

Pascal J. Bourguignon

Daniel T. said:
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();
}
 
A

anon

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
 
B

Bart van Ingen Schenau

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
 
E

Erik Wikström

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

Thomas J. Gritzan

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

khapi

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

khapi

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

peter koch

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
 
D

Daniel Pitts

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.
 

Ask a Question

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

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

No members online now.

Forum statistics

Threads
474,432
Messages
2,571,682
Members
48,796
Latest member
Greg L.

Latest Threads

Top