mirror of
https://gitlab.com/fbb-git/cppannotations
synced 2024-11-18 10:06:54 +01:00
WIP
git-svn-id: https://cppannotations.svn.sourceforge.net/svnroot/cppannotations/trunk@300 f6dd340e-d3f9-0310-b409-bdd246841980
This commit is contained in:
parent
6802f7c8b2
commit
a816374567
6 changed files with 221 additions and 225 deletions
|
@ -14,9 +14,6 @@ includefile(exceptions/example)
|
|||
subsect(Exceptions: the preferred alternative)
|
||||
includefile(exceptions/exception)
|
||||
|
||||
COMMENT(>>>>>>>>>>>>> NEXT <<<<<<<<<<<<<)
|
||||
|
||||
|
||||
lsect(THROW)(Throwing exceptions)
|
||||
includefile(exceptions/throw)
|
||||
|
||||
|
@ -26,6 +23,8 @@ includefile(exceptions/throw)
|
|||
sect(The try block)
|
||||
includefile(exceptions/try)
|
||||
|
||||
COMMENT(>>>>>>>>>>>>> NEXT <<<<<<<<<<<<<)
|
||||
|
||||
lsect(EXCEPTIONCATCH)(Catching exceptions)
|
||||
includefile(exceptions/catch)
|
||||
|
||||
|
|
|
@ -1,55 +1,67 @@
|
|||
The ti(catch) block contains code that is executed when an exception is
|
||||
thrown. Since em(expressions) are thrown, the tt(catch)-block must know what
|
||||
kind of exceptions it should be able to handle. Therefore, the keyword
|
||||
tt(catch) is followed by a parameter list consisting of but one parameter,
|
||||
which is the type of the exception handled by the tt(catch)
|
||||
block. So, an exception handler for tt(char const *) exceptions will have the
|
||||
following form:
|
||||
A ti(catch) clause consists of the keyword tt(catch) followed by a parameter
|
||||
list defining one parameter specifying type and (parameter) name of the
|
||||
exception the tt(catch) handler will catch. This name may then be used as a
|
||||
variable in the compound statement following the tt(catch) clause.
|
||||
Example:
|
||||
verb(
|
||||
catch (char const *message)
|
||||
catch (string &message)
|
||||
{
|
||||
// code to handle the message
|
||||
}
|
||||
)
|
||||
Earlier (section ref(THROW)) we've seen that such a message doesn't have
|
||||
to be thrown as a static string. It's also possible for a function to return a
|
||||
string, which is then thrown as an exception. If such a function
|
||||
creates the string that is thrown as an exception em(dynamically), the
|
||||
exception handler will normally have to delete the allocated memory to prevent
|
||||
a i(memory leak).
|
||||
Primitive types and objects may be thrown as exceptions. It's a
|
||||
bad idea to throw a pointer or reference to a local object, but a pointer to a
|
||||
em(dynamically) allocated object may be thrown as the exception handler will
|
||||
be able to delete the allocated memory thus preventing a i(memory
|
||||
leak). Throwing such a pointer is still dangerous as the exception handler
|
||||
will not be able to distinguish dynamically allocated memory and
|
||||
non-dynamically allocated memory, as illustrated by the next example:
|
||||
verb(
|
||||
try
|
||||
{
|
||||
int x;
|
||||
int *xp = &x;
|
||||
|
||||
Close attention should be paid to the nature of the parameter of the
|
||||
exception handler, to make sure that
|
||||
hi(exception: dynamically generated)
|
||||
dynamically generated exceptions are deleted once the handler has processed
|
||||
them. Of course, when an exception is passed on to an outer level
|
||||
exception handler, the received exception should em(not) be deleted by the
|
||||
inner level handler.
|
||||
if (condition1)
|
||||
throw &xp;
|
||||
|
||||
Different kinds of exceptions may be thrown: tt(char *)s, tt(int)s, pointers
|
||||
or references to objects, etc.: all these different types may be used in
|
||||
throwing and catching exceptions. So, various types of exceptions may come out
|
||||
of a tt(try)-block. In order to i(catch all expressions) that may emerge from
|
||||
a tt(try)-block, multiple exception handlers (i.e., tt(catch)-blocks) may
|
||||
follow the tt(try)-block.
|
||||
xp = new int(0);
|
||||
if (condition2)
|
||||
throw xp;
|
||||
}
|
||||
catch (int *ptr)
|
||||
{
|
||||
// delete ptr or not?
|
||||
}
|
||||
)
|
||||
Close attention should be paid to the nature of the parameter of the
|
||||
exception handler, to make sure that when pointers to dynamically allocated
|
||||
memory are thrown the memory is returned once the handler has processed
|
||||
the pointer. In general pointers should not be thrown as exceptions. If
|
||||
dynamically allocated memory must be passed to a exception handler then the
|
||||
pointer should be wrapped in a smart pointer, like tt(unique_ptr) or
|
||||
tt(shared_ptr) (cf. sections ref(UNIQUEPTR) and ref(SHAREDPTR)).
|
||||
|
||||
To some extent the em(order) hi(exception handler: order) of the exception
|
||||
handlers is important. When an exception is thrown, the first exception
|
||||
handler matching the type of the thrown exception is used and remaining
|
||||
exception handlers are ignored. So only one exception handler following a
|
||||
tt(try)-block will be executed. Normally this is no problem: the thrown
|
||||
exception is of a certain type, and the correspondingly typed catch-handler
|
||||
will catch it. For example, if exception handlers are defined for
|
||||
tt(char *)s and ti(void *)s then ASCII-Z strings will be caught by the latter
|
||||
handler. Note that a tt(char *) can also be considered a tt(void *), but even
|
||||
so, an ASCII-Z string will be handled by a tt(char *) handler, and not by a
|
||||
tt(void *) handler. This is true in general: handlers should be designed very
|
||||
type specific to catch the correspondingly typed exception. For example,
|
||||
tt(int)-exceptions are not caught by tt(double)-catchers, tt(char)-exceptions
|
||||
are not caught by tt(int)-catchers. Here is a little example illustrating that
|
||||
the order of the catchers is not important for types not having any
|
||||
hierarchal relation to each other (i.e., tt(int) is not derived from
|
||||
tt(double); tt(string) is not derived from ASCII-Z):
|
||||
Multiple tt(catch) handlers may follow a tt(try) block, each handler
|
||||
defining its own exception type. The em(order) hi(exception handler: order)
|
||||
of the exception handlers is important. When an exception is thrown, the first
|
||||
exception handler matching the type of the thrown exception is used and
|
||||
remaining exception handlers are ignored. Eventually at most one exception
|
||||
handler following a tt(try)-block will be used. Normally this is of no
|
||||
concern as each exception has its own unique type.
|
||||
|
||||
Example: if exception handlers are defined for tt(char *)s and ti(void *)s
|
||||
then ASCII-Z strings will be caught by the former handler. Note that a tt(char
|
||||
*) can also be considered a tt(void *), but the exception type matching
|
||||
procedure is smart enough to use the tt(char *) handler with the thrown
|
||||
ASCII-Z string. Handlers should be designed very type specific to catch the
|
||||
correspondingly typed exception. For example, tt(int)-exceptions are not
|
||||
caught by tt(double)-catchers, tt(char)-exceptions are not caught by
|
||||
tt(int)-catchers. Here is a little example illustrating that the order of the
|
||||
catchers is not important for types not having any hierarchal relationship to
|
||||
each other (i.e., tt(int) is not derived from tt(double); tt(string) is not
|
||||
derived from ASCII-Z):
|
||||
COMMENT(>>>>>>>>>>>>> NEXT <<<<<<<<<<<<<)
|
||||
verbinclude(exceptions/examples/catchers.cc)
|
||||
As an alternative to constructing different types of exception handlers
|
||||
for different types of exceptions, a specific class can be designed whose
|
||||
|
|
|
@ -1,33 +1,35 @@
|
|||
Situations may occur in which it is required to inspect a thrown
|
||||
exception. Then, depending on the nature of the received exception, the
|
||||
program may continue its normal operation, or a serious event took place,
|
||||
requiring a more drastic reaction by the program. In a server-client situation
|
||||
the client may enter requests to the server into a queue. Every request placed
|
||||
in the queue is normally answered by the server, telling the client that the
|
||||
request was successfully completed, or that some sort of error has
|
||||
occurred. Actually, the server may have died, and the client should be able to
|
||||
discover this calamity, by not waiting indefinitely for the server to reply.
|
||||
Sometimes it is required to inspect a thrown exception. An exception catcher
|
||||
may decide to ignore the exception, to process the exception, to rethrow it
|
||||
after inspection or to change it into another kind of exception. For example,
|
||||
in a server-client application the client may submit requests to the server by
|
||||
entering them into a queue. Normally every request is eventually answered by
|
||||
the server. The server may reply the request was successfully processed, or
|
||||
that some sort of error has occurred. On the other hand, the server may have
|
||||
died, and the client should be able to discover this calamity, by not waiting
|
||||
indefinitely for the server to reply.
|
||||
|
||||
In this situation an intermediate i(exception handler) is called for. A thrown
|
||||
In this situation an intermediate exception handler is called for. A thrown
|
||||
exception is first inspected at the middle level. If possible it is processed
|
||||
there. If it is not possible to process the exception at the middle level, it
|
||||
is passed on, unaltered, to a more superficial level, where the really tough
|
||||
exceptions are handled.
|
||||
|
||||
By placing an hi(throw: empty) em(empty) tt(throw) statement in the code
|
||||
handling an exception the received exception is passed on to the next level
|
||||
that might be able to process that particular type of exception.
|
||||
By placing an hi(throw: empty) em(empty) tt(throw) statement in the exception
|
||||
handler's code the received exception is passed on to the next level that
|
||||
might be able to process that particular type of exception. The em(retrown)
|
||||
exception is never handled by one of its neighboring exception handlers; it
|
||||
is always transferred to an exception handler at a more superficial level.
|
||||
|
||||
In our server-client situation a function
|
||||
verb(
|
||||
initialExceptionHandler(char *exception)
|
||||
initialExceptionHandler(string &exception)
|
||||
)
|
||||
could be designed to do so. The received message is inspected. If it's a
|
||||
simple message it's processed, otherwise the exception is passed on to an
|
||||
outer level. The implementation of tt(initialExceptionHandler()) shows the
|
||||
empty tt(throw) statement:
|
||||
could be designed to handle the tt(string) exception. The received message
|
||||
is inspected. If it's a simple message it's processed, otherwise the exception
|
||||
is passed on to an outer level. In tt(initialExceptionHandler)'s
|
||||
implementation the empty tt(throw) statement is used:
|
||||
verb(
|
||||
void initialExceptionHandler(char *exception)
|
||||
void initialExceptionHandler(string &exception)
|
||||
{
|
||||
if (!plainMessage(exception))
|
||||
throw;
|
||||
|
@ -35,63 +37,57 @@ empty tt(throw) statement:
|
|||
handleTheMessage(exception);
|
||||
}
|
||||
)
|
||||
As we will see below (section ref(EXCEPTIONCATCH)), the empty tt(throw)
|
||||
statement passes on the exception received in a tt(catch)-block. Therefore, a
|
||||
function like tt(initialExceptionHandler()) can be used for a variety of
|
||||
thrown exceptions, as long as the argument used with
|
||||
tt(initialExceptionHandler()) is compatible with the nature of the received
|
||||
exception.
|
||||
Below (section ref(EXCEPTIONCATCH)), the empty tt(throw) statement is used
|
||||
to pass on the exception received by a tt(catch)-block. Therefore, a function
|
||||
like tt(initialExceptionHandler) can be used for a variety of thrown
|
||||
exceptions, as long as their types match tt(initialExceptionHandler)'s
|
||||
parameter, which is a string.
|
||||
|
||||
Does this sound intriguing? Then try to follow the next example, which jumps
|
||||
slightly ahead to the topics covered in chapter ref(POLYMORPHISM). The next
|
||||
example may be skipped, though, without loss of continuity.
|
||||
The next example jumps slightly ahead, using some of the topics covered in
|
||||
chapter ref(POLYMORPHISM). The example may be skipped, though, without
|
||||
loss of continuity.
|
||||
|
||||
We can now state that a i(basic exception handling) class can be constructed
|
||||
from which specific exceptions are derived. Suppose we have a class
|
||||
tt(Exception), containing a member function tt(ExceptionType
|
||||
Exception::severity()). This member function tells us (little wonder!) the
|
||||
severity of a thrown exception. It might be tt(Message, Warning, Mistake,
|
||||
Error) or tt(Fatal). Furthermore, depending on the severity, a thrown
|
||||
exception may contain less or more information, somehow processed by a
|
||||
function tt(process()). In addition to this, all exceptions have a plain-text
|
||||
producing member function, e.g., ti(toString()), telling us a bit more about
|
||||
the nature of the generated exception.
|
||||
A basic exception handling class can be constructed from which specific
|
||||
exception types are derived. Suppose we have a class tt(Exception),
|
||||
containing a member function tt(ExceptionType Exception::severity). This
|
||||
member function tells us (little wonder!) the severity of a thrown
|
||||
exception. It might be tt(Info, Notice, Warning, Error) or tt(Fatal). The
|
||||
information contained in the exception depends on its severity and is
|
||||
processed by a function tt(handle). In addition, all exceptions support a
|
||||
member function like ti(textMsg), returning textual information about the
|
||||
exception in a tt(string).
|
||||
|
||||
Using i(polymorphism), tt(process()) can be made to behave differently,
|
||||
depending on the nature of a thrown exception, when called through a basic
|
||||
tt(Exception) pointer or reference.
|
||||
By defining a polymorphic function tt(handle) it can be made to behave
|
||||
differently, depending on the nature of a thrown exception, when called
|
||||
from a basic tt(Exception) pointer or reference.
|
||||
|
||||
In this case, a program may throw any of these five types of exceptions. Let's
|
||||
assume that the tt(Message) and tt(Warning) exceptions are processable by our
|
||||
tt(initialExceptionHandler()). Then its code would become:
|
||||
In this case, a program may throw any of these five exception types. Let's
|
||||
assume that classes tt(Message) and tt(Warning) were derived from the class
|
||||
tt(Exception), then the tt(handle) function matching the exception type
|
||||
will automatically be called by the following exception catcher:
|
||||
verb(
|
||||
void initialExceptionHandler(Exception const *e)
|
||||
catch(Exception &ex)
|
||||
{
|
||||
cout << e->toString() << endl; // show the plain-text information
|
||||
cout << e.textMsg() << '\n';
|
||||
|
||||
if
|
||||
(
|
||||
e->severity() != ExceptionWarning
|
||||
ex.severity() != ExceptionWarning
|
||||
&&
|
||||
e->severity() != ExceptionMessage
|
||||
ex.severity() != ExceptionMessage
|
||||
)
|
||||
throw; // Pass on other types of Exceptions
|
||||
throw; // Pass on other types of Exceptions
|
||||
|
||||
e->process(); // Process a message or a warning
|
||||
delete e;
|
||||
ex.handle(); // Process a message or a warning
|
||||
}
|
||||
)
|
||||
Due to polymorphism (see chapter ref(POLYMORPHISM)), tt(e->process()) will
|
||||
either process a tt(Message) or a tt(Warning). Thrown exceptions are generated
|
||||
as follows:
|
||||
Now anywhere in the tt(try) block preceding the exception handler
|
||||
tt(Exception) objects or objects of one of its derived classes may be thrown,
|
||||
that will all be caught by the above handler. E.g.,
|
||||
verb(
|
||||
throw new Message(<arguments>);
|
||||
throw new Warning(<arguments>);
|
||||
throw new Mistake(<arguments>);
|
||||
throw new Error(<arguments>);
|
||||
throw new Fatal(<arguments>);
|
||||
throw Info();
|
||||
throw Warning();
|
||||
throw Notice();
|
||||
throw Error();
|
||||
throw Fatal();
|
||||
)
|
||||
All of these exceptions are processable by our
|
||||
tt(initialExceptionHandler()), which may decide to pass exceptions upward for
|
||||
further processing or to process exceptions itself. The polymorphic exception
|
||||
class is developed further in section ref(POLYMORPHEXCEPTION).
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
//PROG
|
||||
#include <iostream>
|
||||
#include <string>
|
||||
using namespace std;
|
||||
|
@ -11,7 +12,7 @@
|
|||
:
|
||||
d_name(name)
|
||||
{
|
||||
cout << "Object constructor of " << d_name << "\n";
|
||||
cout << "Constructor of " << d_name << "\n";
|
||||
}
|
||||
Object(Object const &other)
|
||||
:
|
||||
|
@ -21,13 +22,12 @@
|
|||
}
|
||||
~Object()
|
||||
{
|
||||
cout << "Object destructor of " << d_name << "\n";
|
||||
cout << "Destructor of " << d_name << "\n";
|
||||
}
|
||||
void fun()
|
||||
{
|
||||
Object toThrow("'local object'");
|
||||
|
||||
cout << "Object fun() of " << d_name << "\n";
|
||||
cout << "Calling fun of " << d_name << "\n";
|
||||
throw toThrow;
|
||||
}
|
||||
void hello()
|
||||
|
@ -39,7 +39,6 @@
|
|||
int main()
|
||||
{
|
||||
Object out("'main object'");
|
||||
|
||||
try
|
||||
{
|
||||
out.fun();
|
||||
|
@ -50,17 +49,18 @@
|
|||
o.hello();
|
||||
}
|
||||
}
|
||||
//=
|
||||
/*
|
||||
Generated output:
|
||||
Object constructor of 'main object'
|
||||
Object constructor of 'local object'
|
||||
Object fun() of 'main object'
|
||||
Constructor of 'main object'
|
||||
Constructor of 'local object'
|
||||
Calling fun of 'main object'
|
||||
Copy constructor for 'local object' (copy)
|
||||
Object destructor of 'local object'
|
||||
Destructor of 'local object'
|
||||
Copy constructor for 'local object' (copy) (copy)
|
||||
Caught exception
|
||||
Hello by 'local object' (copy) (copy)
|
||||
Object destructor of 'local object' (copy) (copy)
|
||||
Object destructor of 'local object' (copy)
|
||||
Object destructor of 'main object'
|
||||
Destructor of 'local object' (copy) (copy)
|
||||
Destructor of 'local object' (copy)
|
||||
Destructor of 'main object'
|
||||
*/
|
||||
|
|
|
@ -9,103 +9,98 @@ followed by an expression, defining the thrown exception value. Example:
|
|||
different for exceptions.
|
||||
|
||||
Objects defined locally in functions are automatically destroyed once
|
||||
exceptions thrown by these functions leave these functions. However, if the
|
||||
object itself is thrown, the exception catcher receives
|
||||
hi(throw: copy of objects)
|
||||
a em(copy) of the thrown object. This copy is constructed just before the
|
||||
local object is destroyed.
|
||||
exceptions thrown by these functions leave these functions. This also happens
|
||||
to objects thrown as exceptions. However, just before leaving the function
|
||||
context the object is copied and it is this copy that eventually reaches the
|
||||
approprate tt(catch) clause.
|
||||
|
||||
The next example illustrates this point. Within the function
|
||||
tt(Object::fun()) a local tt(Object toThrow) is created, which is thereupon
|
||||
thrown as an exception. The exception is caught outside of tt(Object::fun()),
|
||||
in tt(main()). At this point the thrown object doesn't actually exist anymore,
|
||||
Let's first take a look at the sourcetext:
|
||||
verbinclude(exceptions/examples/throw.cc)
|
||||
The class tt(Object) defines several simple constructors and members. The
|
||||
copy constructor is special in that it adds the text tt(" (copy)") to the
|
||||
received name, to allow us to monitor the construction and destruction of
|
||||
objects more closely. The member function tt(Object::fun()) generates the
|
||||
exception, and throws its locally defined object. Just before the exception
|
||||
the following output is generated by the program:
|
||||
The following examples illustrates this process.
|
||||
tt(Object::fun) defines a local tt(Object toThrow), that is
|
||||
thrown as an exception. The exception is caught
|
||||
in tt(main). But by then the object originally thrown doesn't exist anymore,
|
||||
and tt(main) received a copy:
|
||||
verbinsert(exceptions/examples/throw.cc)(PROG)
|
||||
tt(Object)'s copy constructor is special in that it defines its name as
|
||||
tt(" (copy)") to which the other object's name is appended. This allow us to
|
||||
monitor the construction and destruction of objects more closely.
|
||||
tt(Object::fun) generates an exception, and throws its locally defined
|
||||
object. Just before throwing the exception the program has produced the
|
||||
following output:
|
||||
verb(
|
||||
Object constructor of 'main object'
|
||||
Object constructor of 'local object'
|
||||
Object fun() of 'main object'
|
||||
Constructor of 'main object'
|
||||
Constructor of 'local object'
|
||||
Calling fun of 'main object'
|
||||
)
|
||||
Now the exception is generated, resulting in the next line of output:
|
||||
When the exception is generated the next line of output is produced:
|
||||
verb(
|
||||
Copy constructor for 'local object' (copy)
|
||||
)
|
||||
The tt(throw) clause receives the local object, and treats it as a value
|
||||
argument: it creates a copy of the local object. Following this, the exception
|
||||
is processed: the local object is destroyed, and the catcher catches an
|
||||
tt(Object), again a i(value parameter). Hence, another copy is
|
||||
created. Therefore, we see the following lines:
|
||||
The local object is passed to tt(throw) where it is treated as a value
|
||||
argument, creating a copy of tt(toThrow). This copy is thrown as the
|
||||
exception, and the local tt(toThrow) object ceases to exist. The thrown
|
||||
exception is now caught by the tt(catch) clause, defining an
|
||||
tt(Object) value parameter. Since this is a em(value) parameter yet another
|
||||
copy is created. Thus, the program writes the following text:
|
||||
verb(
|
||||
Object destructor of 'local object'
|
||||
Destructor of 'local object'
|
||||
Copy constructor for 'local object' (copy) (copy)
|
||||
)
|
||||
Now we are inside the catcher, which displays its message:
|
||||
The tt(catch) block now displays:
|
||||
verb(
|
||||
Caught exception
|
||||
)
|
||||
followed by the calling of the tt(hello()) member of the received
|
||||
object. This member also shows us that we received a em(copy of the copy of
|
||||
the local object) of the tt(Object::fun()) member function:
|
||||
Following this tt(o)'s tt(hello) member is called, showing us that we
|
||||
indeed received a em(copy of the copy) of the original tt(toThrow) object:
|
||||
verb(
|
||||
Hello by 'local object' (copy) (copy)
|
||||
)
|
||||
Finally the program terminates, and its still living objects are now
|
||||
destroyed in their reversed order of creation:
|
||||
Then the program terminates and its remaining objects are now
|
||||
destroyed, reversing their order of creation:
|
||||
verb(
|
||||
Object destructor of 'local object' (copy) (copy)
|
||||
Object destructor of 'local object' (copy)
|
||||
Object destructor of 'main object'
|
||||
Destructor of 'local object' (copy) (copy)
|
||||
Destructor of 'local object' (copy)
|
||||
Destructor of 'main object'
|
||||
)
|
||||
|
||||
If the catcher would have been implemented so as to receive a
|
||||
em(reference) to an object (which you could do by using `tt(catch (Object
|
||||
&o))'), then repeatedly calling the copy constructor would have been
|
||||
avoided. In that case the output of the program would have been:
|
||||
The copy created by the tt(catch) clause clearly is superficial. It can be
|
||||
avoided by defining object em(reference parameters) in tt(catch) clauses:
|
||||
`tt(catch (Object &o))'). The program now produces the following output:
|
||||
verb(
|
||||
Object constructor of 'main object'
|
||||
Object constructor of 'local object'
|
||||
Object fun() of 'main object'
|
||||
Constructor of 'main object'
|
||||
Constructor of 'local object'
|
||||
Calling fun of 'main object'
|
||||
Copy constructor for 'local object' (copy)
|
||||
Object destructor of 'local object'
|
||||
Destructor of 'local object'
|
||||
Caught exception
|
||||
Hello by 'local object' (copy)
|
||||
Object destructor of 'local object' (copy)
|
||||
Object destructor of 'main object'
|
||||
Destructor of 'local object' (copy)
|
||||
Destructor of 'main object'
|
||||
)
|
||||
This shows us that only a single copy of the local object has been used.
|
||||
Only a single copy of tt(toThrow) was created.
|
||||
|
||||
Of course, it's a bad idea to hi(throw: pointer to a local object) throw a
|
||||
em(pointer) to a locally defined object: the pointer is thrown, but the object
|
||||
to which the pointer refers dies once the exception is thrown, and the catcher
|
||||
receives a i(wild pointer). Bad news....
|
||||
It's a bad idea to hi(throw: pointer) throw a em(pointer) to a locally defined
|
||||
object. The pointer is thrown, but the object to which the pointer refers
|
||||
ceases to exist once the exception is thrown. The catcher receives a
|
||||
i(wild pointer). Bad news....
|
||||
|
||||
Summarizing:
|
||||
Let's summarize the above findings:
|
||||
itemization(
|
||||
it() hi(throw: local objects) Local objects are thrown as copied
|
||||
objects,
|
||||
it() Pointers to local objects should not be thrown.
|
||||
it() However, it is hi(throw: pointer to a dynamically generated object)
|
||||
possible to throw pointers or references to em(dynamically) generated
|
||||
it() Local objects are thrown as copied objects;
|
||||
it() Don't throw pointers to local objects;
|
||||
it() It is possible to throw pointers to em(dynamically) generated
|
||||
objects. In this case one must take care that the generated object is properly
|
||||
deleted when the generated exception is caught, to prevent a i(memory leak).
|
||||
deleted by the exception handler to prevent a i(memory leak).
|
||||
)
|
||||
Exceptions hi(exceptions: when) are thrown in situations where a function
|
||||
can't continue its normal task anymore, although the program is still able to
|
||||
continue. Imagine a program which is an interactive calculator. The program
|
||||
continuously requests expressions, which are then evaluated. In this case the
|
||||
parsing of the expression may show syntactic errors; and the evaluation of
|
||||
the expression may result in expressions which can't be evaluated, e.g.,
|
||||
because of the expression resulting in a i(division by zero). Also, the
|
||||
calculator might allow the use of variables, and the user might refer to
|
||||
i(non-existing variables): plenty of reasons for exceptions to be thrown, but
|
||||
no overwhelming reason to terminate the program. In the program, the following
|
||||
code may be used, all throwing exceptions:
|
||||
Exceptions are thrown in situations where a function can't complete its
|
||||
assigned task, but the program is still able to continue. Imagine a program
|
||||
offering an interactive calculator. The program expects numeric expressions,
|
||||
which are evaluated. Expressions may show syntactic errors or it may be
|
||||
mathematically impossible to evaluate them. Maybe the calculator allows us to
|
||||
define and use variables and the user might refer to non-existing variables:
|
||||
plenty of reasons for the expression evaluation to fail, and so many reasons
|
||||
for exceptions to be thrown. None of those should terminate the
|
||||
program. Instead, the program's user is informed about the nature of the
|
||||
problem and is invited to enter another expression. Example:
|
||||
verb(
|
||||
if (!parse(expressionBuffer)) // parsing failed
|
||||
throw "Syntax error in expression";
|
||||
|
@ -116,14 +111,12 @@ code may be used, all throwing exceptions:
|
|||
if (divisionByZero()) // unable to do division
|
||||
throw "Division by zero is not defined";
|
||||
)
|
||||
The hi(location of throw statements) location of these tt(throw)
|
||||
statements is immaterial: they may be placed deeply nested within the program,
|
||||
or at a more superficial level. Furthermore, em(functions) may be used to
|
||||
generate hi(throw: function return values) the expression which is then
|
||||
thrown. A function
|
||||
centt(char const *formatMessage(char const *fmt, ...);)
|
||||
would allow us to throw more specific messages, like
|
||||
Where these tt(throw) statements are located is irrelevant: they may be
|
||||
found deeply nested inside the program, or at a more superficial level.
|
||||
Furthermore, em(functions) may be used to generate the exception to be
|
||||
thrown. An tt(Exception) object might support stream-like insertion operations
|
||||
allowing us to do, e.g.,
|
||||
verb(
|
||||
if (!lookup(variableName))
|
||||
throw formatMessage("Variable '%s' not defined", variableName);
|
||||
throw Exception() << "Undefined variable '" << variableName << "';
|
||||
)
|
||||
|
|
|
@ -1,30 +1,26 @@
|
|||
The ti(try)-block surrounds statements in which exceptions may be thrown. As
|
||||
we have seen, the actual tt(throw) statement can be placed everywhere, not
|
||||
necessarily directly in the tt(try)-block. It may, for example, be placed in a
|
||||
function, called from within the tt(try)-block.
|
||||
The ti(try)-block surrounds tt(throw) statements. Remember that a program is
|
||||
always surrounded by a global tt(try) block, so tt(throw) statements may
|
||||
appear anywhere in your code. More often, though, tt(throw) statements are
|
||||
used in function bodies and such functions may be called from within tt(try)
|
||||
blocks.
|
||||
|
||||
The keyword tt(try) is followed by a set of curly braces, acting like a
|
||||
standard bf(C++) i(compound statement): multiple statements and
|
||||
definitions may be placed here.
|
||||
|
||||
It is possible (and very common) to create hi(exception: levels) em(levels) in
|
||||
which exceptions may be thrown. For example, tt(main())'s code is surrounded
|
||||
by a tt(try)-block, forming an outer level in which exceptions can be handled.
|
||||
Within tt(main())'s tt(try)-block, functions are called which may also contain
|
||||
tt(try)-blocks, forming the next level in which exceptions may be
|
||||
generated. As we have seen (in section ref(EMPTYTHROW)), exceptions thrown in
|
||||
A tt(try) block is defined by the keyword tt(try) followed by a compound
|
||||
statement. This block, in turn, em(must) be followed by at least one
|
||||
tt(catch) handler:
|
||||
verb(
|
||||
try
|
||||
{
|
||||
// any statements here
|
||||
}
|
||||
catch(...) // at least one catch clause here
|
||||
{}
|
||||
tt(Try)-blocks are commonly nested, creating exception em(levels). For
|
||||
example, tt(main)'s code is surrounded by a tt(try)-block, forming an outer
|
||||
level handling exceptions. Within tt(main)'s tt(try)-block functions are
|
||||
called which may also contain tt(try)-blocks, forming the next exception
|
||||
level. As we have seen (section ref(EMPTYTHROW)), exceptions thrown in
|
||||
inner level tt(try)-blocks may or may not be processed at that level. By
|
||||
placing an hi(throw: empty) empty tt(throw) in an exception handler, the
|
||||
placing an empty tt(throw) statement in an exception handler, the
|
||||
thrown exception is passed on to the next (outer) level.
|
||||
|
||||
If an exception hi(exception: outside of try block)
|
||||
is thrown outside of any tt(try)-block, then the default
|
||||
hi(exception: default handling) way to handle (uncaught) exceptions is
|
||||
used, which is normally to i(abort) the program. Try to compile and run the
|
||||
following tiny program, and see what happens:
|
||||
verb(
|
||||
int main()
|
||||
{
|
||||
throw "hello";
|
||||
}
|
||||
)
|
||||
|
||||
|
|
Loading…
Reference in a new issue