1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111
|
Exceptions are generated by ti(throw) statements. The tt(throw) keyword is
followed by an expression, defining the thrown exception value. Example:
verb( throw "Hello world"; // throws a char *
throw 18; // throws an int
throw string{ "hello" }; // throws a string)
Local objects cease to exist when a function terminates. This is no
different for exceptions.
Objects defined locally in functions are automatically destroyed once
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
appropriate tt(catch) clause.
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:
verbinclude(//PROG examples/throw.cc)
tt(Object)'s copy constructor is special in that it defines its name as
the other object's name to which the string tt(" (copy)") 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( Constructor of 'main object'
Constructor of 'local object'
Calling fun of 'main object')
When the exception is generated the next line of output is produced:
verb( Copy constructor for 'local object' (copy))
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( Destructor of 'local object'
Copy constructor for 'local object' (copy) (copy))
The tt(catch) block now displays:
verb( Caught exception)
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))
Then the program terminates and its remaining objects are now
destroyed, reversing their order of creation:
verb( Destructor of 'local object' (copy) (copy)
Destructor of 'local object' (copy)
Destructor of 'main object')
The copy created by the tt(catch) clause clearly is superfluous. 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( Constructor of 'main object'
Constructor of 'local object'
Calling fun of 'main object'
Copy constructor for 'local object' (copy)
Destructor of 'local object'
Caught exception
Hello by 'local object' (copy)
Destructor of 'local object' (copy)
Destructor of 'main object')
Only a single copy of tt(toThrow) was created.
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....
Let's summarize the above findings:
itemization(
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 by the exception handler to prevent a i(memory leak).
)
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";
if (!lookup(variableName)) // variable not found
throw "Variable not defined";
if (divisionByZero()) // unable to do division
throw "Division by zero is not defined";)
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 Exception() << "Undefined variable '" << variableName << "';)
|