mirror of
https://gitlab.com/fbb-git/cppannotations
synced 2024-11-18 10:06:54 +01:00
WIP - Exceptions
git-svn-id: https://cppannotations.svn.sourceforge.net/svnroot/cppannotations/trunk@309 f6dd340e-d3f9-0310-b409-bdd246841980
This commit is contained in:
parent
423e5e09e1
commit
b774566c52
5 changed files with 183 additions and 17 deletions
|
@ -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
43
yo/exceptions/nothrow.yo
Normal 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.
|
||||
|
||||
|
||||
|
|
@ -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)).
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in a new issue