📄 exceptionhandling.htm
字号:
<HTML>
<head>
<style>
CODE {COLOR: #990000;}
.code{COLOR: #990000}
.codeComment{COLOR: #008000}
.codeHighlight{BACKGROUND-COLOR: #FFFF00}
.codeFileName{FONT-WEIGHT: bold;}
</style>
<meta http-equiv="Content-Type"
content="text/html; charset=iso-8859-1">
<meta name="Author" content="Mike Gradman">
<meta name="KeyWords"
content="DTL, Oracle, ODBC, database API, C++, Template Library">
<meta name="GENERATOR" content="Microsoft FrontPage Express 2.0">
<!--
-- Copyright 2000
-- Michael Gradman & Corwin Joy
--
-- Permission to use, copy, modify, distribute and sell this software
-- and its documentation for any purpose is hereby granted without fee,
-- provided that the above copyright notice appears in all copies and
-- that both that copyright notice and this permission notice appear
-- in supporting documentation. Corwin Joy & Michael Gradman make no
-- representations about the suitability of this software for any
-- purpose. It is provided "as is" without express or implied warranty.
--
--
-- Copyright (c) 1996-1999
-- Silicon Graphics Computer Systems, Inc.
--
-- Permission to use, copy, modify, distribute and sell this software
-- and its documentation for any purpose is hereby granted without fee,
-- provided that the above copyright notice appears in all copies and
-- that both that copyright notice and this permission notice appear
-- in supporting documentation. Silicon Graphics makes no
-- representations about the suitability of this software for any
-- purpose. It is provided "as is" without express or implied warranty.
--
-- Copyright (c) 1994
-- Hewlett-Packard Company
--
-- Permission to use, copy, modify, distribute and sell this software
-- and its documentation for any purpose is hereby granted without fee,
-- provided that the above copyright notice appears in all copies and
-- that both that copyright notice and this permission notice appear
-- in supporting documentation. Hewlett-Packard Company makes no
-- representations about the suitability of this software for any
-- purpose. It is provided "as is" without express or implied warranty.
--
-->
<!-- Generated by htmldoc -->
<title>Exception Safety in DTL</title>
</head>
<body bgcolor="#FFFFFF" text="#000000" link="#0000EE"
vlink="#551A8B" alink="#FF0000">
<p><font size="6" face="Bookman Old Style"><em><strong><u>dtl</u></strong></em></font></p>
<p><img src="stat.gif" width="6" height="6"> <!--end header--> <br>
</p>
<H1>Exception Safety in DTL</H1>
<I><FONT ><STRONG><H1>by Mike Gradman and Corwin Joy</H1>
<P>(Adapted from </I></FONT></STRONG><A HREF="http://www.stlport.org/doc/exception_safety.html"><I><FONT COLOR="#0000ff"><STRONG>Exception Safety in STLPort </I></FONT></STRONG></A><I><FONT ><STRONG>by Dave Abrahams)</P>
</I></FONT></STRONG><H2><A NAME="guarantees"></A>Basic Library Guarantees</H2>
<B><P>DTL makes the guarantee that</B> <B>no resources are leaked in the face of exceptions</B>.</P>
<B><P>This means:</P>
<UL>
</B><LI>By the time a container's destructor completes: </LI>
<UL>
<LI>It has returned all memory it has allocated to the appropriate deallocation function. </LI>
<LI>The destructor has been called for all objects constructed by the container. </LI></UL>
<LI>Algorithms destroy all temporary objects and deallocate all temporary memory even if the algorithm does not complete due to an exception. </LI>
<LI>Algorithms which construct objects (e.g. <TT>uninitialized_fill</TT>) either complete successfully or destroy any objects they have constructed at the time of the exception. </LI>
<LI>Algorithms which destroy objects always succeed. </LI></UL>
<H4>Additionally:</H4>
<UL>
<LI>Algorithms which operate on ranges of objects leave only fully-constructed objects in those ranges if they terminate due to an exception. </LI>
<LI>Containers continue to fulfill all of their requirements, even after an exception occurs during a mutating function. For example, an IndexedDBView will never give an inaccurate report of its size, or fail to meet its performance requirements because the tree that implements it has become unbalanced. </LI>
<LI>A stronger guarantee is available for some operations: that <I>if the operation terminates due to an exception, program state will remain unchanged</I>. For example, <TT>DBView<...>::set_io_handler()</TT> leaves the view unchanged if an exception is thrown, provided the library client fulfills the <A HREF="#basic_requirements">basic requirements</A> below. For some operations, the "<A HREF="#Strong Guarantee">strong guarantee</A>" is only available if additional requirements are filled. </LI></UL>
<B><FONT SIZE=5><P><A NAME="basic_requirements"></A>Basic Client Requirements</P>
</FONT><P>The </B><A HREF="#guarantees"><B>library guarantees</B></A><B> above are conditional on some requirements that library clients must fulfill.</P>
<UL>
</B><LI>Destructors of any client classes used by the library may not throw exceptions. This includes all classes used as library template parameters. </LI>
<LI>The underlying STL library used to compile DTL must be exception safe. It must make the exception safety guarantees provided by STLport or given in the C++ standard. </LI>
<LI>Autocommit must be turned off for any <FONT FACE="Courier New" SIZE=2 >DBConnection</FONT> objects used. Otherwise, operations may have side effects and leave the system in an inconsistent state, leaving us with no guarantees at all. </LI></UL>
<H2><A NAME="Strong_Guarantee"></A>The "Strong Guarantee"</H2>
<P>In many programs, some objects will be destroyed automatically during exception-unwinding. For these, the basic guarantee that resources won't be leaked is good enough. If a program hopes to survive an exception and continue running, though, it probably also uses long-lived containers which are expected to survive past exception-recovery in a known state. For example, a program could maintain a list of objects representing tasks it is working on. If adding a task to that list fails, the program may still need to rely on the list. If the list must survive an exception intact, we need the strong guarantee:</P>
<UL>
<I><LI>If an exception is thrown, the operation has no effects.</I> </LI></UL>
<P>You can get the strong guarantee by "brute force" for any container operation as follows, provided the container's <TT>swap()</TT> member function can't fail (this is true for most real-world containers):</P>
<TT><P>container_type container_copy( original_container ); <BR>
container_copy.mutating_operation(...); <BR>
original_container.swap( container_copy );</P>
</TT><P>Fortunately, many mutating operations give the strong guarantee with no additional requirements on the client. To get the strong guarantee for others, you can either use the above technique or conform to some <A HREF="#additional_requirements">additional requirements</A>. This guarantee is also known as the aCi guarantee, i.e. the operation is atomic - all or nothing, consistent, and effects are isolated. Details on this kind of ACID analysis can be found at <A HREF="http://www.gotw.ca/gotw/061.htm">"Guru of the Week #61"</A>.</P>
<P> </P>
<H2>Operations that give a "no-throw" guarantee</H2>
<TT><P>All swap() member functions and class destructors.</P>
</TT><P> </P>
<H2>Operations that give the "strong guarantee" if the <A HREF="#basic_requirements">basic requirements</A><I> </I>have been met.</H2>
<TT><P>All, unless otherwise listed.</P>
<P>Special note for </TT><FONT FACE="Courier New" SIZE=2>IndexedDBView<View> </FONT><TT>member functions. The strong guarantee here excludes call to fetch() -- most members call fetch() to initialize the object if it is not already built from the database which technically means the operation has a possibly non-atomic effect on the object. When we say these operations are atomic we exclude effects from the initial fetch().</P>
</TT><P> </P>
<H2>Operations that give no guarantee.</H2>
<UL>
<FONT FACE="Courier New" SIZE=2><LI>DB_iterator<DataObj, ParamObj></FONT>::<TT>Params(const ParamObj &params). This assigns into the ParamObj * held by the iterator. Guarantees here are only as strong as the assignment guarantees given by the client.</TT> </LI>
<TT><LI>DBView<DataObj, ParamObj>::insert/update/delete_iterator operator=(const DataObj &data) -- proxy assignment operator for output iterators. This assigns into the DataObj * held by the iterator and then calls InsValidate on that data object. Guarantees are only as strong as given by these two functions -- however if assignment does not succeed entirely the exception will be caught and data will be marked as invalid and not allowed to be inserted. See the discussion below on exceptions with iterators for more on this topic.</TT> </LI></UL>
<P> </P>
<H2>Theory of Exception Handling for Input and Output Iterators</H2>
<FONT FACE="Courier New" SIZE=2 ><P>(Extracted from discussions in comp.c++.std. Many thanks to Dave Abrahams for his comments &<BR>
criticisms. Any errors here are my own - and doubtless come from ignoring his advice :-) Corwin).</P>
<P>Some definitions:<BR>
InputIterator and OutputIterator - by this I will mean the generic InputIterator and OutputIterator concepts in STL. Specific examples in DTL are DBView::select, insert, update and delete.</FONT> <BR>
<BR>
<FONT FACE="Courier New" SIZE=2>i/o Iterator - shorthand for "InputIterator or OutputIterator" <BR>
<BR>
"Abraham's Strong Guarantee" or "aCi", by this I mean the ACID transactional guarantee as defined in "Guru of the Week #61",</FONT><A HREF="http://www.peerdirect.com/resources/gotw061a.html"><FONT FACE="Courier New" SIZE=2>http://www.peerdirect.com/resources/gotw061a.html</FONT></A><FONT FACE="Courier New" SIZE=2>, i.e. the operation is <BR>
a = atomic, if the operation fails the object will be in its initial state, otherwise in its final state <BR>
C = consistent, if op succeeds or fails the system will be in a 'Consistent' state with no integrity constraints violated, no memory leaked etc. <BR>
i = isolated, if operation fails, no side effects including any changes to the database. <BR>
<BR>
Claim 1: Currently the STL makes aCi guarantees for some of its operations. These aCi guarantees are only with respect to the STL object acted upon and make no promise about state of the iterators that they act upon. My claim is that if we require iterators to be aCi over a range, then STL operations over that range can be "jointly aCi" in that both the effect on the STL object and the effect on the iterator can be made simultaneously aCi. <BR>
<BR>
Claim 2: Often the "jointly aCi" guarantee over a range is not what you want for an algorithm. Instead, if we assume the each individual operation to read or write to our i/o Iterator is aCi, then we may want a "maximal" property which says that we will try to execute the maximum possible number of successful atomic transactions against the given iterator. In this case, without 'range atomicity' on the underlying iterator I think the best we can hope for is consistency at the end of the algorithm. <BR>
<BR>
Definition: An iterator is aCi over a range if it displays: <BR>
a: atomicity - the iterator must support transaction semantics. In other words the iterator must be able to mark the beginning on a transaction on a range, and have commands to commit and rollback changes posted to the range since the beginning of the transaction. <BR>
C: we say an iterator is consistent if all operations on that iterator are consistent over the range <BR>
i: we say that an iterator is isolated over a range if any changes made by that iterator are not visible outside of the iterator until committed. <BR>
Proof of Claim 1:
if: if an iterator x, is aCi and an algorithm f, is aCi then we may obtain f(x) aCi via the following pseudo-code </P>
</FONT><CODE><PRE>
try {
	f(x); // operation(s) to apply changes over a range
	commit x;
}
catch(...) {
	// oops, we could not change the entire range, rollback any changes
	rollback x;
}
</PRE>
</CODE><FONT FACE="Courier New" SIZE=2><P>Example of Claim 1.<BR>
Suppose we define a custom class called foo which can throw on construction and try to initialize an array of foo via a call to<BR>
foo *p = new foo[5];<BR>
What happens if the third element constructed throws? The compiler will then internally destruct the first and second elements built before the throw. So, conceptually, what the compiler's operator new does is this:</P>
</FONT><PRE>operator new[] { // new an array of objects
try {
iterator i = array.begin();
*i = newly constructed object;
i++;
}
catch(...) {
// oops, we couldn't construct all objects, attempt to roll-back by invoking destructors
while (--i != array.rend() )
destruct(*i);
}
}
</PRE>
<FONT FACE="Courier New" SIZE=2><P><BR>
Example of Claim 2.<BR>
<BR>
Now consider what happens when we invoke operator delete[] over a range<BR>
<BR>
foo *p = new foo[5]; delete[] p;<BR>
<BR>
What happens if the third element destructed throws? According to the standard, the results are undefined. One major reason for this is that the compiler does not have an easy way to "roll-back" range destruction operations as it cannot easily decrement the iterator and re-construct elements similar to what is done for the new[] operator. In other words, the "delete iterator" used by operator delete[] cannot give "range atomicity" as defined above. This is one reason why the conventional exception safety wisdom is to suppress errors in destructors so that we can get a "maximal guarantee" for operator delete[]. I.e. every item that can be successfully destructed in the array gets destroyed. <BR>
<BR>
Claim 2: There are going to be times when your i/o Iterator either cannot provide an atomic range guarantee, or you don't want your algorithm to act in an atomic fashion over a range. Instead, if we assume the each individual operation to read or write to our i/o Iterator is aCi, then we may want a "maximal" property which says that the maximum possible number of successful atomic transactions will be executed against the given iterator. </P>
<P>Definition: We say that an i/o Iterator is element-wise aCi if reading or writing an individual element is aCi. This means that for an OutputIterator, called out, *out = data, must be aCi. For an InputIterator, called in, data=*in, must be aCi. <BR>
<BR>
Definition: We say that an iterator has gone "bad" if it is no longer able to read from or write to its underlying data source. Concrete examples would be <BR>
(1) An ostream_iterator to a file where the underlying ostream can no longer write to the file. <BR>
(2) A database input iterator, where the connection to the database is lost and it can no longer read records from the database table it is pointing at. <BR>
(3) A directory iterator, such as the one by Dietmar Kuehl at </FONT><A HREF="http://www.boost.org/libs/dir_it/dir_it.html"><FONT FACE="Courier New" SIZE=2>http://www.boost.org/libs/dir_it/dir_it.html</FONT></A><FONT FACE="Courier New" SIZE=2>, where the connection to the underlying drive is lost and further filenames cannot be retrieved. </P>
<P>Definition: We say that an iterator read/write operation has "failed" if a particular data element cannot be read from or written to an iterator but the iterator has not gone "bad". Usually such a read "failure" will be caused by that particular element not matching a type or business rule that is required for each individual data object. <BR>
Examples: <BR>
(1) A database iterator tries to write the following rows to a table whose only field is a date field: '01-Jan-1980', 'lala', '01-Jan-1981'. Here the second element would 'fail' because it is not a valid date that can be written to the table. <BR>
(2) An istream_iterator<int> tries to read the following comma delimited elements from a file: 1, 2, lala, 4. The element 'lala' cannot be converted to an <int> and hence 'fails'. </P>
<P>Some iterators have a natural notion of atomic elements (rows in a table, filenames in a directory). For others, if an element 'fails' the bad data can affect other elements e.g. an istream_iterator tries to read 1, 2, 345, 6 where 4 is a corrupted byte that has replaced the comma delimiter between 3 and 5 leading to the incorrectly read number '345'. This lack of element atomicity is a property of the iterator being worked with and is independent of the 'fail' concept since an element can be 'successfully' read but still hold a bad value. To get the 'maximal' property we can do the following: <BR>
<BR>
1. Impose a client requirement that the user defined 'data' type cannot throw on construction or assignment. <BR>
<BR>
2. For InputIterators use the following logic for ++ (and for construction when reading the first element); On ++, try to read the next 'data' element. If fail(), push the resulting 'data' element into an error buffer and try to get the next element e.g. </P>
</FONT><PRE>while (iterator != end-of-stream) {
read information into internal 'data' object;
if (not fail() and not bad() )
break;
if (bad()) {
iterator = end-of-stream;
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -