[D.Abrahams ESGC] David Abrahams, "Exception-Safety in Generic Components", http://www.boost.org/more/generic_exception_safety.html
[S.Meyers, 2005] Scott Meyers, "Effective C++"
[B.Stroustrup, 2000] Bjarne Stroustrup, "The C++ Programming Language, Special Edition"
Jackson Sun, "如何编写异常安全的C++代码"
The trick shown above, using class to manage the lifecycle of an raw resource is generally referred as RAII in the C++ community.
RAII stands for "Resource Acquisition Is Initialization".
std::auto_ptr<T> is generally an demonstration of RAII in the standard library.
Containers from the STL expects its containing objects has the normal value semantics, low cost of copying and destructing. Unfortunately std::auto_ptr has its unusual copy semantics hence CAN'T be used in STL containers.
If desired, use tr1::shared_ptr (formally boost::shared_ptr) for unsuitable RAII classes, or make it an light, normal value class instead.
STL the Standard Template Library has settled the style of C++ language in general use (in certain degree)
Upon exceptions, there are styles and no-throw guarantees followed by STL compatible classes:
Value semantics (copy constructor and operator= must be implemented, right)
no-throw for destructor and swap() if provided. Specialize std::swap function template recommended over member swap() function. [S.Meyers, 2005]
Extensively use of const interface
Extensively use const-ref for parameter passing, use PBV in case const-ref won't serve the purpose.
The class ResourceNs has taken the responsibility taking care of the "raw" resources, i.e. network sockets, database connections, etc.
Java GC only works with memory. Other resources are required to be managed manually (e.g. .close() of all those *Streams). That means the ResourceNs are not implementable in Java reasonably. Hence...
The Java equivalent is just far toooooo lengthy to fit on this slide...
{ // pretending in a constructor, allocating resource for members
// remember the C-style code?
std::auto_ptr<R1> p1 (new Resource1());
std::auto_ptr<R2> p2 (new Resource2());
std::auto_ptr<R3> p3 (new Resource3());
this->pR1 = p1.release();
this->pR2 = p2.release();
this->pR3 = p3.release();
// last 3: std::auto_ptr's ops are
// compliant to the no-throw guarantee
}
The magic is all within the *_Handle classes. A simple example here
struct FPPool_Handle {
FPPool_Handle(FPPoolRef const& h)
: handle_(h)
{}
~FPPool_Handle ()
{ FPPool_Close(handle_); }
private:
FPPoolRef handle_;
};
Basic guarantee for all operations:
The invariants of the component are preserved,
and no resources are leaked. [D.Abrahams ESGC]
Strong guarantee for key operations:
The operation has either completed successfully or thrown an exception, leaving the program state exactly as it was before the operation started. [D.Abrahams ESGC]
The no-throw guarantee:
The operation will not throw an exception.
[D.Abrahams ESGC]
As mentioned before, any instruction in the same block bellow won't get executed
Problem:
char* buffer = new char[N];
tendToGetSoInsecure(buffer); // when throw
delete[] buffer; // no one will touch here..
We need our code "exception-safe"
Expected (standard) exceptions:
std::bad_alloc if new failed
std::bad_cast if dynamic_cast failed
More. Check them out from Section 14.10 "Standard Exceptions" [B.Stroustrup 2000]
Remember: All C++ exceptions are EXPECTED!!!
Accessing NULL / wild pointers will NOT cause an C++ exception hence can't be catched!!
Unix signals / Win32 SEH are NOT C++ exceptions!!
If you want to catch something, make sure someone is throwing it.
Recommended exception handling
Throw by value, catch by reference.
Exception may have class hierarchies, catch by reference will allow to handle a "class" of exceptions all together.
try clause may have multiple catch clauses followed and is selected in an "first match" matter so sequence matters!
catch (...) means generally the same as
catch (Throwable t) in Java. Information of caught exception is lost within this catch clause.
So, What's so COOOOOOL about stack unwinding?
All auto variable have their ~T() called
Manually manipulated (newed, fopened, etc) resources untouched to guarantee the free will of programmer.
Control flow handled back to the caller.
Program kept in "exception handling" state, so caller need no special treatment if don't know what to do.
All automated process.
Have global fall-back mechanism and can be customized (std::set_terminate).
So, let's do it the unexpected way.
throw std::exception();
What will happen?
Normal execution flow interrupted (any instruction in the same block bellow won't get executed)
Looking for corresponding catch clause, unwinding stack if not found
std::terminate() called if uncaught at all.
More problems
Error in nested loops / nested function calls
Can't chain up function calls
outer (inner()); /* How do we handle failure in inner() ? */
Messes up error handling flow with normal logic
Not everyone knows how to handle every error, but redundant logic must present
if ( FAILED == lastReturnValue ) {
return error_indicator;
}