WIP - Exceptions

git-svn-id: https://cppannotations.svn.sourceforge.net/svnroot/cppannotations/trunk@309 f6dd340e-d3f9-0310-b409-bdd246841980
This commit is contained in:
Frank B. Brokken 2009-11-29 13:25:44 +00:00
parent 423e5e09e1
commit b774566c52
5 changed files with 183 additions and 17 deletions

View file

@ -38,8 +38,6 @@ includefile(exceptions/iostreams)
lsect(STDEXC)(Standard Exceptions)
includefile(exceptions/standard)
COMMENT(>>>>>>>>>>>>> NEXT <<<<<<<<<<<<<)
sect(Exception guarantees)
includefile(exceptions/guarantees)
@ -49,6 +47,11 @@ includefile(exceptions/guarantees)
subsect(The strong guarantee)
includefile(exceptions/strong)
subsect(The nothrow guarantee)
includefile(exceptions/nothrow)
COMMENT(>>>>>>>>>>>>> NEXT <<<<<<<<<<<<<)
lsect(FUNTRY)(Function try blocks)
includefile(exceptions/function)

43
yo/exceptions/nothrow.yo Normal file
View file

@ -0,0 +1,43 @@
Exception safety can only be realized if some functions and operations are
guaranteed em(not) to throw exceptions. This is called the
emi(nothrow guarantee). An example of a function that must offer the nothrow
guarantee is the tt(swap) function. Consider once again the canonical
overloaded assignment operator:
verb(
Class &operator=(Class const &other)
{
Class tmp(other);
swap(other);
return *this;
}
)
If tt(swap) would be allowed to throw exceptions then it would
most likely leave the current object in a partially swapped state. As a result
the current object's state would most likely have been changed. As tt(tmp)
will have been destroyed by the time a catch handler receives the thrown
exception it will be very difficult (as in: impossible) to retrieve the
object's original state. Losing the strong guarantee as a consequence.
The ti(swap) function must therefore offer the nothrow guarantee. It must
have been designed as if using the following prototype (don't do this: see
also section ref(THROWLIST)):
verb(
void Class::swap(Class &other) throw();
)
Likewise, tt(operator delete) and tt(operator delete[]) offer the nothrow
guarantee, and according to the bf(C++) standard destructors may themselves
not throw exceptions (if they do their behavior is formally undefined, see
also section ref(CONSEXCEPTIONS) below).
Since the bf(C) programming language does not define the exception concept
functions from the standard bf(C) library offer the nothrow guarantee
by implication. This allowed us to define the generic tt(swap) function in
section ref(CopyDestroy) using bf(memcpy)(3).
Operations on primitive types offer the nothrow guarantee. Pointers may be
reassigned, references may be returned etc. etc. without having to worry about
exceptions that might be thrown.

View file

@ -21,4 +21,114 @@ data type) is also guaranteed not to throw exceptions. The canonical form of
the overloaded assignment operator therefore meets the requirements of the
strong guarantee.
Some hi(rule of thumb) rules of thumb were formulated that relate to the
strong guarantee
(cf. i(Sutter, H.), emi(Exceptional C++), Addison-Wesley, 2000). E.g.,
itemization(
it() All the code that might throw an exception affecting the current
state of an object should perform its tasks separately from the data
controlled by the object. Once this code has performed its tasks without
throwing an exception replace the object's data by the new data.
it() Member functions modifying their object's data should not return
original (contained) objects by value.
)
The canonical assignment operator is a good example of the first rule of
thumb. Another example is found in classes storing objects. Consider a class
tt(PersonDb) storing multiple tt(Person) objects. Such a class might offer a
member tt(void add(Person const &next)). A plain implementation of this
function (merely intended to show the application of the first rule of thumb,
but otherwise completely disregarding efficiency considerations) might be:
verb(
void PersonDb::newAppend(Person const &next)
{
Person *tmp = 0;
try
{
tmp = new Person[d_size + 1];
for (size_t idx = 0; idx < d_size; ++idx)
tmp[idx] = d_data[idx];
tmp[d_size] = next;
}
catch (...)
{
delete[] tmp;
throw;
}
return tmp;
}
void PersonDb::add(Person const &next)
{
Person *tmp = newAppend(next);
delete[] d_data;
d_data = tmp;
++d_size;
}
)
The (private) tt(newAppend) member's task is to create a copy of the
currently allocated tt(Person) objects, including the data of the next
tt(Person) object. Its tt(catch) handler catches any exception that might be
thrown during the allocation or copy process and will return all memory
allocated so far, rethrowing the exception. The function is
emi(exception neutral) as it propagates all its exceptions to its caller. The
function also doesn't modify the tt(PersonDb) object's data, so it meets the
strong exception guarantee. Returning from tt(newAppend) tt(addPerson) may now
modify its data. Its existing data are returned and its tt(d_data) pointer is
made to point to the newly created array of tt(Person) objects. Finally its
tt(d_size) is incremented. As these three steps don't throw exceptions tt(add)
too meets the strong guarantee.
The second rule of thumb (member functions modifying their object's data
should not return original (contained) objects by value) may be illustrated
using a member tt(PersonDb::erase(size_t idx)). Here is an implementation
attempting to return the original tt(d_data[idx]) object:
verb(
Person PersonData::erase(size_t idx)
{
if (idx >= d_size)
throw string("Array bounds exceeded");
Person ret(d_data[idx]);
Person *tmp = copyAllBut(idx);
delete[] d_data;
d_data = tmp;
--d_size;
return ret;
}
)
Although copy elision will usually prevent the copy constructor from being
used when returning tt(ret), this is not guaranteed to happen and a copy
constructor em(may) throw an exception. If that happens the function has
irrevocably mutated the tt(PersonDb)'s data, thus losing the strong guarantee.
Rather than returning tt(d_data[idx]) by value it might be assigned to an
external tt(Person) object befor mutating tt(PersonDb)'s data:
verb(
void PersonData::erase(Person *dest, size_t idx)
{
if (idx >= d_size)
throw string("Array bounds exceeded");
*dest = d_data[idx];
Person *tmp = copyAllBut(idx);
delete[] d_data;
d_data = tmp;
--d_size;
}
)
This modification works, but changes the original assignment of creating a
member returning the original object. However, both functions suffer from a
task overload as they modify tt(PersonDb)'s data and also return an original
object. In situations like these the em(one-function-one-responsibility)
i(rule of thumb) should be kept in mind: a function should have a single, well
defined responsibility.
The preferred approach therefore is to retrieve tt(PersonDb)'s objects using a
member like tt(Person const &at(size_t idx) const) and to erase an object
using a separate member like tt(void PersonData::erase(size_t idx)).

View file

@ -28,11 +28,21 @@ ostream) hi(operator<<(): and manipulators) has an overloaded oplshift()
accepting a i(pointer to a function) expecting an tt(ostream &) and returning
an tt(ostream &). Its definition is:
verb(
ostream& operator<<(ostream & (*func)(ostream &str))
ostream& operator<<(ostream ostream &(*func)(ostream &str))
{
return (*func)(*this);
}
)
In addition to the above overloaded oplshift() another one is defined
verb(
ostream &operator<<(ios_base &(*func)(ios_base &base))
{
(*func)(*this);
return *this;
}
)
This latter function is used when inserting, e.g., tt(hex) or
tt(internal).
The above procedure does not work for i(manipulators requiring arguments):
it is of course possible to overload oplshift() to accept an

View file

@ -39,16 +39,16 @@ otherwise.
ittq(string &append(char const *argument, size_type an))
(the first tt(an) characters of tt(argument) are appended to the
string object.)
ittq(string &append(size_type n, char c))
(tt(n) characters tt(c) are appended to the current string object.)
ittq(string &append(size_type n, char ch))
(tt(n) characters tt(ch) are appended to the current string object.)
ithtq(assign)(string &assign(string const &argument, size_type
apos, size_type an))
(tt(argument) (or a substring) is assigned to the string object.
If tt(argument) is of type tt(char const *) and one additional
argument is provided the second argument is interpreted as a value
initializing tt(an), using 0 to initialize tt(apos).)
ittq(string &assign(size_type n, char c))
(tt(n) characters tt(c) are assigned to the current string object.)
ittq(string &assign(size_type n, char ch))
(tt(n) characters tt(ch) are assigned to the current string object.)
ithtq(capacity)(size_type capacity() const)
(the number of characters that can currently be stored in the string
object without needing to resize it is returned.)
@ -125,8 +125,8 @@ otherwise.
(the first index in the current string object where tt(argument) is
found is returned. When all three arguments are specified the first
argument em(must) be a tt(char const *).)
ittq(size_t find(char c, size_type opos) const)
(the first index in the current string object where tt(c) is found is
ittq(size_t find(char ch, size_type opos) const)
(the first index in the current string object where tt(ch) is found is
returned.)
ithtq(find_first_of)(size_t find_first_of(string const &argument,
size_type opos) const)
@ -143,12 +143,12 @@ otherwise.
characters of the tt(char const *) argument that should be used in the
search. It defines a substring starting at the beginning of
tt(argument). If omitted, all of tt(argument)'s characters are used.)
ittq(size_type find_first_of(char c, size_type opos))
(the first index in the current string object where character tt(c) is
ittq(size_type find_first_of(char ch, size_type opos))
(the first index in the current string object where character tt(ch) is
found is returned.)
ittq(size_t find_first_not_of(char c, size_type opos) const)
ittq(size_t find_first_not_of(char ch, size_type opos) const)
(the first index in the current string object where another
character than tt(c) is found is returned.)
character than tt(ch) is found is returned.)
ithtq(find_last_of)(size_t find_last_of(string const &argument,
size_type opos) const)
(the last index in the current string object where any character in
@ -164,8 +164,8 @@ otherwise.
characters of the tt(char const *) argument that should be used in the
search. It defines a substring starting at the beginning of
tt(argument). If omitted, all of tt(argument)'s characters are used.)
ittq(size_type find_last_of(char c, size_type opos))
(the last index in the current string object where character tt(c) is
ittq(size_type find_last_of(char ch, size_type opos))
(the last index in the current string object where character tt(ch) is
found is returned.)
ithtq(find_last_not_of)(size_t find_last_not_of(string const &argument,
size_type opos) const)
@ -206,7 +206,7 @@ otherwise.
(tt(Count) characters tt(ch) are inserted at index tt(opos) into the
current string object.)
ittq(string::iterator insert(string::iterator begin, char ch))
(the character tt(c) is inserted at the current object's position
(the character tt(ch) is inserted at the current object's position
referred to by tt(begin). tt(Begin) is returned.)
ittq(string::iterator insert(string::iterator begin, size_t count,
char ch))
@ -224,7 +224,7 @@ otherwise.
ithtq(max_size)(size_t max_size() const)
(the maximum number of characters that can be stored in the current
string object is returned.)
itt(replace)(string &replace+OPENPARsize_t opos, size_t on, string const
itht(replace)(string &replace+OPENPARsize_t opos, size_t on, string const
&)linebreak()
tt(argument, size_type apos, size_type an+CLOSEPAR):
quote(a (sub)string of characters in tt(object) are replaced by the