Whitepaper:
The New Information Agenda. Do You Have One?
|
Whitepaper:
The Outsourcing Decision for a Globally Integrated Enterprise: From Commodity Outsourcing to Value Creation
|
Whitepaper:
How are Other CIOs Driving Growth?
|
Whitepaper:
Master Data Management: Looking Beyond the Single View to Find the Right View
|
Cleaning
up
Part
of the magic of exception handling is that you can pop from normal program flow
into the appropriate exception handler. This wouldn’t be very useful,
however, if things weren’t cleaned up properly as the exception was
thrown. C++ exception handling guarantees that as you leave a scope, all
objects in that scope
whose
constructors have been completed
will have destructors called.
Here’s
an example that demonstrates that constructors
that aren’t completed don’t have the associated destructors called.
It also shows what happens when an exception is thrown in the middle of the
creation of an array of objects, and an
unexpected( )
function that rethrows the unexpected exception:
//: C23:Cleanup.cpp
// Exceptions clean up objects
#include <fstream>
#include <exception>
#include <cstring>
using namespace std;
ofstream out("cleanup.out");
class Noisy {
static int i;
int objnum;
static const int sz = 40;
char name[sz];
public:
Noisy(const char* nm="array elem") throw(int){
objnum = i++;
memset(name, 0, sz);
strncpy(name, nm, sz - 1);
out << "constructing Noisy " << objnum
<< " name [" << name << "]" << endl;
if(objnum == 5) throw int(5);
// Not in exception specification:
if(*nm == 'z') throw char('z');
}
~Noisy() {
out << "destructing Noisy " << objnum
<< " name [" << name << "]" << endl;
}
void* operator new[](size_t sz) {
out << "Noisy::new[]" << endl;
return ::new char[sz];
}
void operator delete[](void* p) {
out << "Noisy::delete[]" << endl;
::delete []p;
}
};
int Noisy::i = 0;
void unexpected_rethrow() {
out << "inside unexpected_rethrow()" << endl;
throw; // Rethrow same exception
}
int main() {
set_unexpected(unexpected_rethrow);
try {
Noisy n1("before array");
// Throws exception:
Noisy* array = new Noisy[7];
Noisy n2("after array");
} catch(int i) {
out << "caught " << i << endl;
}
out << "testing unexpected:" << endl;
try {
Noisy n3("before unexpected");
Noisy n4("z");
Noisy n5("after unexpected");
} catch(char c) {
out << "caught " << c << endl;
}The
class
Noisy
keeps track of objects so you can trace program progress. It keeps a count of
the number of objects created with a
static
data member
i,
and the number of the particular object with
objnum,
and a character buffer called
name
to hold an identifier. This buffer is first set to zeroes. Then the constructor
argument is copied in. (Note that a default argument string is used to indicate
array elements, so this constructor also acts as a default constructor.)
Because the Standard C library function
strncpy( )stops
copying after a null terminator
or
the number of characters specified by its third argument, the number of
characters copied in is one minus the size of the buffer, so the last character
is always zero, and a print statement will never run off the end of the buffer.
There
are two cases where a
throw
can occur in the constructor. The first case happens if this is the fifth
object created (not a real exception condition, but demonstrates an exception
thrown during array construction). The type thrown is
int,
which is the type promised in the exception specification. The second case,
also contrived, happens if the first character of the argument string is
‘z’,
in which case a
char
is thrown. Because
char
is not listed in the exception specification, this will cause a call to
unexpected( ). The
array versions of
new
and
delete
are overloaded for
the class, so you can see when they’re called.
The
function
unexpected_rethrow( )
prints a message and rethrows the same exception. It is installed as the
unexpected( )
function in the first line of
main( ).
Then some objects of type
Noisy
are created in a
try
block, but the array causes an exception to be thrown, so the object
n2
is never created. You can see the results in the output of the program:
constructing Noisy 0 name [before array]
Noisy::new[]
constructing Noisy 1 name [array elem]
constructing Noisy 2 name [array elem]
constructing Noisy 3 name [array elem]
constructing Noisy 4 name [array elem]
constructing Noisy 5 name [array elem]
destructing Noisy 4 name [array elem]
destructing Noisy 3 name [array elem]
destructing Noisy 2 name [array elem]
destructing Noisy 1 name [array elem]
Noisy::delete[]
destructing Noisy 0 name [before array]
caught 5
testing unexpected:
constructing Noisy 6 name [before unexpected]
constructing Noisy 7 name [z]
inside unexpected_rethrow()
destructing Noisy 6 name [before unexpected]
Four
array elements are successfully created, but in the middle of the constructor
for the fifth one, an exception is thrown. Because the fifth constructor never
completes, only the destructors for objects 1–4 are called.
The
storage for the array is allocated separately with a single call to the global
new.
Notice that even though
delete
is never explicitly called anywhere in the program, the exception-handling
system knows it must call
delete
to properly release the storage. This behavior happens only with
“normal” versions of
operator
new
.
If you use the placement syntax
described in Chapter XX, the exception-handling mechanism will not call
delete
for that object because then it might release memory that was not allocated on
the heap.
Finally,
object
n1
is destroyed, but not object
n2
because it was never created.
In
the section testing
unexpected_rethrow( ),
the
n3
object is created, and the constructor of
n4
is begun. But before it can complete, an exception is thrown. This exception is
of type
char,
which violates the exception specification, so the
unexpected( )
function is called (which is
unexpected_rethrow( ),
in this case). This rethrows the same exception, which is expected this time,
because
unexpected_rethrow( )
can throw any type of exception. The search begins right after the constructor
for
n4,
and the
char
exception handler catches it (after destroying
n3,
the only successfully created object). Thus, the effect of
unexpected_rethrow( )
is to take any unexpected exception and make it expected; used this way it
provides a filter to allow you to track the appearance of unexpected exceptions
and pass them through.