Re: Exception handling and encapsulation
* benben:
Hello C++ experts,
This is rather a design question, I must say. My question is, how do you
write exception safe code while ensuring encapsulation?
In C++, if a function by itself does not know how to handle an exception
raised from a lower level system, we usually implement the function in
such a way that it is exception safe, yet allow the exception to be
propagated to a higher level system.
However, since each system level defines a certain abstraction, allowing
exceptions to propagate through multiple system levels may breach
encapsulation, because it exposes low level implementation details. The
exception hence can be less useful than it should be, as the higher
level system will have little knowledge how such low level exceptions
are to be handled accurately.
Furthermore, should the higher level system choose to handle exceptions
from multiple layers down, it risks to be turned invalid as soon as
changes are made to the lower level systems. This is very similar to
accessing a class member that is ought to be private.
An exception just says "I failed", possibly augmented with "because".
When you receive an exception you have just two options: achieve the
goal anyway, or in turn fail and report that via an exception.
In some cases the "because" is critical to select the proper way to
achieve the goal in spite of the exception, e.g. possible retry versus
trying some other way to do the same versus informing the user (or
whatever that's allowed as success by the function's contract). In that
case the exception generally needs to be handled by the immediate
caller, or at least not very far up the call chain, where there is
knowledge of possible "because" reasons. Otherwise it doesn't really
matter where in the call chain the exception is finally caught.
What you write about exposing low level implementation details is only
relevant for logging and reporting exceptions.
For logging you want as much detail as practically possible, extracted
from the exception object and the immediate call context for where the
exception originated, plus possibly the entire call chain, versus for an
interactive user you usually want a highest level description possible,
extracted from the exception object or possibly just the exception
occurrence, plus the highest level call context, the highest level goal
to be achieved. These are contradictory requirements. Happily logging
and reporting to the user do not need to be done by the same code... ;-)
To give an example, consider the following program, which consists of
three distinct system levels: at the lowest, a database class; up one
level, an enrolment form; and at the highest level, the main function
that uses the enrolment forms.
#include <iostream>
#include <sstream>
// Incomplete program, will not link.
///////////////////////////////////////////////////
// Low level system
class connection_error{};
class IO_error{};
class repetition_error{};
Use standard exception classes, i.e. derive from std::exception, or even
better (in general) from std::runtime_error.
class database
{
public:
void connect(std::string location) throw (connection_error);
And don't use exception specifications (except possibly empty ones).
void create_record(std::string rc) throw (IO_error,
repetition_error);
// ...
};
Here is the dilemma:
1) If main() is to handle the exceptions from submit1() member function
call, it will have to deal with exceptions from both enrolment_form
(mid-level) and database (low-level.) Since the use of a database is to
be encapsulated from main(), the writer of main() obviously knows little
how to handle such exceptions.
Just do
catch( std::exception const& ) { ... }
Cheers, & hth.,
- Alf
--
A: Because it messes up the order in which people normally read text.
Q: Why is it such a bad thing?
A: Top-posting.
Q: What is the most annoying thing on usenet and in e-mail?