//: C01:SafeAssign.cpp
// Shows an Exception-safe operator=
#include
#include // For std::bad_alloc
#include
using namespace std;
// A class that has two pointer members using the heap
class HasPointers {
// A Handle class to hold the data
struct MyData {
const char* theString;
const int* theInts;
size_t numInts;
MyData(const char* pString, const int* pInts,
size_t nInts)
: theString(pString), theInts(pInts),
numInts(nInts) {}
} *theData; // The handle
// clone and cleanup functions
static MyData* clone(const char* otherString,
const int* otherInts, size_t nInts){
char* newChars = new char[strlen(otherString)+1];
int* newInts;
try {
newInts = new int[nInts];
} catch (bad_alloc&) {
delete [] newChars;
throw;
}
try {
// This example uses built-in types, so it won't
// throw, but for class types it could throw, so we
// use a try block for illustration. (This is the
// point of the example!)
strcpy(newChars, otherString);
for (size_t i = 0; i < nInts; ++i)
newInts[i] = otherInts[i];
} catch (...) {
delete [] newInts;
delete [] newChars;
throw;
}
return new MyData(newChars, newInts, nInts);
}
static MyData* clone(const MyData* otherData) {
return clone(otherData->theString,
otherData->theInts,
otherData->numInts);
}
static void cleanup(const MyData* theData) {
delete [] theData->theString;
delete [] theData->theInts;
delete theData;
}
public:
HasPointers(const char* someString, const int* someInts,
size_t numInts) {
theData = clone(someString, someInts, numInts);
}
HasPointers(const HasPointers& source) {
theData = clone(source.theData);
}
HasPointers& operator=(const HasPointers& rhs) {
if (this != &rhs) {
MyData* newData =
clone(rhs.theData->theString,
rhs.theData->theInts,
rhs.theData->numInts);
cleanup(theData);
theData = newData;
}
return *this;
}
~HasPointers() {
cleanup(theData);
}
friend ostream& operator<<(ostream& os,
const HasPointers& obj) {
os << obj.theData->theString << ": ";
for (size_t i = 0; i < obj.theData->numInts; ++i)
os << obj.theData->theInts[i] << ' ';
return os;
}
};
int main() {
int someNums[] = {1, 2, 3, 4};
size_t someCount = sizeof someNums / sizeof someNums[0];
int someMoreNums[] = {5, 6, 7};
size_t someMoreCount =
sizeof someMoreNums / sizeof someMoreNums[0];
HasPointers h1("Hello", someNums, someCount);
HasPointers h2("Goodbye", someMoreNums, someMoreCount);
cout << h1 << endl; // Hello: 1 2 3 4
h1 = h2;
cout << h1 << endl; // Goodbye: 5 6 7
} ///:~
For convenience, HasPointersuses the MyDataclass as a handle to the two pointers. Whenever it’s time to allocate more memory, whether during construction or assignment, the first clonefunction is ultimately called to do the job. If memory fails for the first call to the newoperator, a bad_allocexception is thrown automatically. If it happens on the second allocation (for theInts), we have to clean up the memory for theString—hence the first tryblock that catches a bad_allocexception. The second tryblock isn’t crucial here because we’re just copying ints and pointers (so no exceptions will occur), but whenever you copy objects, their assignment operators can possibly cause an exception, in which case everything needs to be cleaned up. In both exception handlers, notice that we rethrow the exception. That’s because we’re just managing resources here; the user still needs to know that something went wrong, so we let the exception propagate up the dynamic chain. Software libraries that don’t silently swallow exceptions are called exception neutral . Always strive to write libraries that are both exception safe and exception neutral. [6] If you’re interested in a more in-depth analysis of exception safety issues, the definitive reference is Herb Sutter’s Exceptional C++ , Addison-Wesley, 2000.
If you inspect the previous code closely, you’ll notice that none of the deleteoperations will throw an exception. This code actually depends on that fact. Recall that when you call deleteon an object, the object’s destructor is called. It turns out to be practically impossible, therefore, to design exception-safe code without assuming that destructors don’t throw exceptions. Don’t let destructors throw exceptions! (We’re going to remind you about this once more before this chapter is done) [7] The library function uncaught_exception( ) returns true in the middle of stack unwinding, so technically you can test uncaught_exception( ) for false and let an exception escape from a destructor. We’ve never seen a situation in which this constituted good design, however, so we only mention it in this footnote.
.
Programming with exceptions
For most programmers, especially C programmers, exceptions are not available in their existing language and take a bit of adjustment. Here are some guidelines for programming with exceptions .
Exceptions aren’t the answer to all problems. In fact, if you simply go looking for something to pound with your new hammer, you’ll cause trouble. The following sections point out situations in which exceptions are not warranted. Probably the best advice for deciding when to use exceptions is to throw exceptions only when a function fails to meet its specification .
Not for asynchronous events
The Standard C signal( )system and any similar system handle asynchronous events: events that happen outside the flow of a program, and thus events the program cannot anticipate. You cannot use C++ exceptions to handle asynchronous events because the exception and its handler are on the same call stack. That is, exceptions rely on the dynamic chain of function calls on the program’s runtime stack (dynamic scope, if you will), whereas asynchronous events must be handled by completely separate code that is not part of the normal program flow (typically, interrupt service routines or event loops). Don’t throw exceptions from interrupt handlers .
This is not to say that asynchronous events cannot be associated with exceptions. But the interrupt handler should do its job as quickly as possible and then return. The typical way to handle this situation is to set a flag in the interrupt handler, and check it synchronously in the mainline code .
Not for benign error conditions
If you have enough information to handle an error, it’s not an exception. Take care of it in the current context rather than throwing an exception to a larger context .
Also, C++ exceptions are not thrown for machine-level events such as divide-by-zero. [8] Some compilers do throw exceptions in these cases, but they usually provide a compiler option to disable this (unusual) behavior.
It’s assumed that some other mechanism, such as the operating system or hardware, deals with these events. In this way, C++ exceptions can be reasonably efficient, and their use is isolated to program-level exceptional conditions .
Читать дальше