When to use exceptions

K

kelvSYC

What I want to do is to read a 32-bit unsigned integer (let's call that
u32) in little-endian form from an fstream. Would it be better if my
function went like this:

// returns false if an error occurs reading the value
bool read(std::fstream& stream, u32& value);

or this:

u32 read(std::fstream& stream) throw(FileReadingException);

In general, what are some good guidelines for when something like the
first is preferred over something like the second, and vice-versa?
 
A

Alf P. Steinbach

* kelvSYC:
What I want to do is to read a 32-bit unsigned integer (let's call that
u32) in little-endian form from an fstream. Would it be better if my
function went like this:

// returns false if an error occurs reading the value
bool read(std::fstream& stream, u32& value);

or this:

u32 read(std::fstream& stream) throw(FileReadingException);

Given only these two to choose from:

the first and only the first.

But also provide a throwing version like the second but without exception
specification.



In general, what are some good guidelines for when something like the
first is preferred over something like the second

Always. Exception specifications are very impractical and misleading in
C++. There are only a few forms that are acceptable, in certain contexts.
 
K

kelvSYC

// returns false if an error occurs reading the value
Given only these two to choose from:

the first and only the first.

But also provide a throwing version like the second but without exception
specification.

So a comprimise like so:

// throws exception if an error occurs reading the value, but is not
// specified in the prototype
u32 read(std::fstream& stream);

would be acceptable C++ style then?
 
A

Alf P. Steinbach

* kelvSYC:
So a comprimise like so:

// throws exception if an error occurs reading the value, but is not
// specified in the prototype
u32 read(std::fstream& stream);

would be acceptable C++ style then?

1. What if you need i32 result, double result, and so forth?

2. What if you'd like to use this for a stream that's not an fstream?

3. The earlier quandary seems to indicate a non-throwing version can
come in handy, and there's no need to exclude it.

So perhaps

template< typename T >
void throwIf0( T nonzero, char const diagnostic[] )
{
if( !nonzero ){ throw std::runtime_error( diagnostic ); }
}

bool readBinary( std::istream& stream, u32& result ){ ... }

u32 u32FromBinary( std::istream& stream )
{
u32 result;
throwIf0( read( stream, result ), "my useful diagnostic" );
return result;
}

For text I'd just read text strings and convert them to whatever
they represent, because such functions are much cleaner and more
reusable, and because stream based text-to-other conversion is not
well-defined in C++ (the interface is there, the definition not).
 
N

Niels Dybdahl

What I want to do is to read a 32-bit unsigned integer (let's call that
u32) in little-endian form from an fstream. Would it be better if my
function went like this:

// returns false if an error occurs reading the value
bool read(std::fstream& stream, u32& value);

or this:

u32 read(std::fstream& stream) throw(FileReadingException);

I prefer to throw when an error occurs. This ensures me that the execution
of the calling function stops. Without throwing I might forget handling the
error condition and such errors are hard to find during test.

Niels Dybdahl
 
D

David

I prefer to throw when an error occurs. This ensures me that the execution
of the calling function stops. Without throwing I might forget handling the
error condition and such errors are hard to find during test.

Niels Dybdahl

My preference, as the designer and developer of a function, is to provide
adequate documentation about the behavior and abilities of said function.
As the user of functions that others have developed, I want to see such
documentation as well.

My reasoning is that at any location in a given fragment of code, an
error may occur. Some errors are more likely than others. Exception
based error reporting enforces the user to wrap all code levels with
appropriate exception handling code. This must be done often enough
that the user can actually find and correct the cause of an error
when it occurs. For any given level of functionality, there are many
possible errors to report -- whether they may be from exceptions or
other known sources.

Given a well documented interface, the user can then decide what
is an appropriate use for their needs. This includes handling
unexpected conditions.

Users need to handle errors at a reasonable level. Your preference
to use an exception to report "an expected file error" may be appropriate
for one developer and not another.

My preference for using exceptions is to use them very sparingly
and with good cause. Before exceptions were common, return codes
and other methods were commonplace. The user had to decide at what level
to check for these unexpected conditions. Exceptions give us a different
method of reporting errors. Now users are faced with handling both
methods all of the time. Your code could be quite complicated for
handling information (expected and unexpected behavior) for any non-trivial
function. When designing interfaces, please make it easy to understand
what the expected behavior is. I should be able to use nearly all
interfaces by reading just the interface documentation (.h and other files).
You should make it easy for the user to handle all errors and behaviors
appropriately. That has different meanings for different environments.
So, consider your user base when designing an interface.

Your suggestion that the interface provide both non-exception and
exception based functions is a good one. However, when that function
is non-trivial, you may find that writing both versions at a level that
can handle all errors appropriately may be a daunting task. The two
functions may not even look the same. Keeping them in-sync could be
difficult.

David
 
R

Rolf Magnus

Niels said:
I prefer to throw when an error occurs. This ensures me that the execution
of the calling function stops. Without throwing I might forget handling
the error condition and such errors are hard to find during test.

However, if the calling code needs to know exactly where the reading failed,
it leads to excessive typing:

struct somestruct
{
long a;
long b;
long c;
};

//...

somestruct s;
try
{
s.a = read(stream);
}
catch (FileReadingException& ex)
{
//... whatever
return; // or throw or whatever
}

try
{
s.b = read(stream);
}
catch (FileReadingException& ex)
{
//... whatever
return; // or throw or whatever
}

try
{
s.c = read(stream);
}
catch (FileReadingException& ex)
{
//... whatever
return; // or throw or whatever
}

//...
 
?

=?ISO-8859-15?Q?Juli=E1n?= Albo

kelvSYC said:
// returns false if an error occurs reading the value
bool read(std::fstream& stream, u32& value);

or this:

u32 read(std::fstream& stream) throw(FileReadingException);

In general, what are some good guidelines for when something like the
first is preferred over something like the second, and vice-versa?

IMO that depends of the file format. If, for example, you are reading until
eof, the first way is best. If the format is fixed, and you know how much
data is in the file and a failure to read means that the file is corrupt or
unreadable, the second way allows cleaner code.
 
?

=?ISO-8859-15?Q?Juli=E1n?= Albo

Rolf said:
However, if the calling code needs to know exactly where the reading
failed, it leads to excessive typing:

struct somestruct
{
long a;
long b;
long c;
};

//...

somestruct s;
try
{
s.a = read(stream);
}
catch (FileReadingException& ex)
{
//... whatever
return; // or throw or whatever
}

try
{
s.b = read(stream);
}
catch (FileReadingException& ex)
{
//... whatever
return; // or throw or whatever
}

You can do something like that:

void readsome ( ..... , StatReadSome & stat)
{
.....
stat= ReadSomeNothing;
s.a= read (stream);
stat= ReadSome_a;
s.b= read (stream);
stat= ReadSome_b;
....
}
 
N

Niels Dybdahl

I prefer to throw when an error occurs. This ensures me that the
execution
However, if the calling code needs to know exactly where the reading failed,
it leads to excessive typing:

struct somestruct
{
long a;
long b;
long c;
};

//...

somestruct s;
try
{
s.a = read(stream);
}
catch (FileReadingException& ex)
{
//... whatever
return; // or throw or whatever
}

try
{
s.b = read(stream);
}
catch (FileReadingException& ex)
{
//... whatever
return; // or throw or whatever
}

try
{
s.c = read(stream);
}
catch (FileReadingException& ex)
{
//... whatever
return; // or throw or whatever
}

//...

No. You can just do:

s.a = read(stream);

s.b = read(stream);

s.c = read(stream);

No error checking code means that there is much less error checking code to
test. How often do you make systematic test of your error checking code ?

Niels Dybdahl
 
R

Rolf Magnus

Niels said:
No. You can just do:

s.a = read(stream);

s.b = read(stream);

s.c = read(stream);

And how do I know whether s.c really contains a value read from the file?
No error checking code means that there is much less error checking code
to test.

And it leads to programs that print messages like "There was some error."
without letting the user know what the exact error actually was, because
that information got lost due to the missing error checking code.
How often do you make systematic test of your error checking code
?

You mean, you don't test how your program reacts to various errors in the
input data?
 
N

Niels Dybdahl

No. You can just do:
And how do I know whether s.c really contains a value read from the file?

It does. If an error occurred, the execution is breaked by an exception.
And it leads to programs that print messages like "There was some error."
without letting the user know what the exact error actually was, because
that information got lost due to the missing error checking code.


You mean, you don't test how your program reacts to various errors in the
input data?

No I do not mean that. I mean: do you go through your sourcecode to locate
all your error checking code and create test cases so that each error
checking code is tested for correct behaviour ? It is not nice to send out
an application where numerous code lines have never been executed.

Niels Dybdahl
 
R

Rolf Magnus

Niels said:
It does. If an error occurred, the execution is breaked by an exception.

Ok, bad example. What about s.b then? If an exception during reading s.c is
thrown, s.b contains a valid value, if an exception is thrown earlier, it
doesn't.
No I do not mean that. I mean: do you go through your sourcecode to locate
all your error checking code and create test cases so that each error
checking code is tested for correct behaviour ?
No.

It is not nice to send out an application where numerous code lines have
never been executed.

And you think it's better to leave that code out completely?
 
N

Niels Dybdahl

No. You can just do:
Ok, bad example. What about s.b then? If an exception during reading s.c is
thrown, s.b contains a valid value, if an exception is thrown earlier, it
doesn't.

So if an exception occurs, you have to treat all values gained in this
functionality as invalid. Code designed on returned error values have exact
the same problem. For local variables it is not a problem as they are
automatically destructed and removed. For global variables you will always
have the problem: What to do if the structure is only partly created due to
an error ?
And you think it's better to leave that code out completely?

If the error still can be handled, as it can with exceptions, then yes. Deep
into the "read" function there are still error checking code that can throw
and I hope that the author of that function has tested that. At a high level
I would have a try/catch block that catches all errors and reports the
errors to the operator or to a log. I might have a few try/catch blocks for
cleaning up, but I try to avoid that as much as possible and use destructors
instead.
This has two advantages: Less untested code and my code consists of less
error handling, so that is easier to read and maintain.

Niels Dybdahl
 

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,206
Messages
2,571,069
Members
47,675
Latest member
KevinStepp

Latest Threads

Top