Topic: standard library exception safety?


Author: kanze@lts.sel.alcatel.de (James Kanze US/ESC 60/3/141 #40763)
Date: 1996/10/01
Raw View
In article <324BF554.661@elastic.avid.com> "Garth A. Dickie"
<dickie@fantasia.avid.com> writes:

|> James Kanze US/ESC 60/3/141 #40763 wrote:
|> > |> I have been unable to find much discussion of exception safety
|> > |> in the C++ standard library.  I would like to know what the
|> > |> behavior of, say, vector< T > is in the presence of exceptions.
|> >
|> > So would a lot of other people.  There has been some discussion of this
|> > from time to time in this forum, but for the moment, nothing official
|> > from the committee.  The problem definitly wasn't treated in the last
|> > public draft.

|> I have been thinking about it some more, and it seems hard to say anything
|> meaningful in the presence of non-default allocators (what do you do if
|> the assignment operator on the const_reference type throws an exception?!?).

|> It would be relatively easy to lay down some minimal guidelines, but then
|> people will want to strengthen them to the strongest possible statements,
|> which is much more difficult.

|> Here is the level of detail that I would find useful:

My comments which follow should be considered as constructive criticism
of some of the details.  On the whole, I admire the work which has gone
into this proposal...

|>     By a "well-behaved class" we mean a class whose destructor never
|>     throws an exception.

I think that const functions, or at least some const functions, should
be equally considered.  For example, it seems reasonable to me that the
comparison functions used in map, set and some of the algorithms not
throw an exception.

And of course, formally, the rule is that they do not exit by means of
an exception.  If they throw an exception, but catch it before
returning, this is not a problem.

|> There should probably be a requirement that
|>     all classes defined in the standard library are well-behaved whenever
|>     their template arguments and all public or protected types declared
|>     in those template arguments are well-behaved.  (Exactly which
|>     types?  It doesn't really matter if a contained class declares
|>     non-well-behaved classes, but it does if an allocator class has
|>     a poorly-behaved pointer type.)

This creates a problem if the rule of not exiting via an exception is
extended to const functions.  In particular, I can think of
implementations in which basic_string::c_str could throw an exception,
although it is const.  On the other hand, I do think that the library
should guarantee that ordered comparison will not throw an exception; if
the implementation uses a representation of string in which
basic_string::data may throw an exception, then it may not use this
function in its implementation of string comparisons.

|>     By a "well-behaved allocator" we mean an allocator which throws an
|>     exception only from the method allocate( size_type ), and such that
|>     no operations on objects of the types pointer, const_pointer,
|>     reference, const_reference, size_type, etc may throw an exception.
|>     (Exactly which operations?)

What about an implementation which uses an extended integer as
difference_type (or even size_type), and throws an exception on
overflow?

|>     Require that the default allocator be well-behaved.

|>     Require that any container class, when instantiated with a well-behaved
|>     allocator and containing objects of a well-behaved class, will eventually
|>     call free on all pointers allocated with allocate.  (This is difficult
|>     to gaurantee without a well-behaved allocator, since assigning the
|>     pointer returned by allocate might throw an exception otherwise...)
|>         This means that the container, in methods which allocate temporary
|>     memory, must put almost everything after the allocation into a try
|>     block, catch( ... ), free the temporary memory, and throw;.
|>     It won't have to worry about exceptions from destroy, since it is
|>     containing objects of a well-behaved class.
|>         This seems like the bare minimum that should be true in order for
|>     the standard library to be useful in robust code; otherwise there
|>     will be memory leaks in the presence of exceptions.  This is already
|>     some extra work for implementors; the Modena library of 4/96 does not
|>     have this property.

In addition, you have to consider construction/destruction.  Basically,
for any object for which the constructor has been called and has
returned other than by an exception, the container will call the
destructor at some unspecified time (but obviously, before freeing the
underlying memory).

Also, if this is all we want to require, it should be explicitly stated
that once a function of the container has returned via an exception, the
only legal action on that container is to destruct it.  (Basically, it
is undefined if the partially constructed sub-objects are cleaned up
immediatly, or in the destructor.)

|>     By a "well-behaved iterator" we mean an iterator which does not
|>     throw an exception from its copy constructor, assignment operator,
|>     or (of course) destructor.  Is it possible to make the iostream
|>     iterators well-behaved?

A "well-behaved iterator" does not throw an exception, period, for
operations which are legal on it?  I don't think that there is a
problem with the iostream iterators; the iostream only uses exceptions
on a user-choice basis.  (Or can it throw bad_alloc on buffer
allocation?)

|>     By a "well-behaved container" we mean a container which, when instantiated
|>     with a well-behaved allocator and containing objects of a well-behaved
|>     class, always returns new iterators which are well-behaved, and if
|>     always passed well-behaved iterators as arguments, always returns
|>     well-behaved iterators.

|>     Require that the container classes in the standard library all be
|>     well-behaved.

|>     By a "copy-safe" class we mean a class whose copy constructor and operator =
|>     will never throw an exception.  Observe that the built-in types and all
|>     POD-classes are copy-safe.

|>     By a "well-behaved operation" we mean an operation which, when operating
|>     on well-behaved containers and iterators, instantiated with well-behaved
|>     classes and allocators, properly destroys all temporary objects created
|>     during its execution in the presence of exceptions.
|>     It should also, if the container is instantiated on a copy-safe class, make
|>     some appropriate gaurantee depending on the nature of the operation --
|>     for instance, insert might might gaurantee that the container retains its
|>     original state (if the contained object is not copy-safe, insert might gauarantee
|>     that all of the original objects remain in the container, with at most one
|>     appearing twice; but this gets harder to specify);
|>     sort might gauarantee that all of the original objects remain in the container,
|>     and that the container has its original size (if the contained object is not
|>     copy-safe, sort might gaurantee nothing at all...)

|>     Require that all of the usual operations push_back, pop_front, insert, etc.
|>     and all of the algorithms provided in the standard library are well-behaved.

|> > |> 1) If vector<T>::push_back( const T & ) fails with a bad_alloc
|> > |>    exception thrown, what is the precise state of the vector
|> > |>    which was being operated on?  Is it gauranteed to be in the
|> > |>    same state that it was in before the failed push_back call?
|> >
|> > I would guess that this is rather safe.  After all, the vector must do
|> > the allocation before it can do anything else:-).

|> I think that this could be required of well-behaved containers, but
|> the requirement that the allocator be well-behaved is neccessary.

|> > More generally, although I think push_back can be written to
|> > successfully guarantee no change if an exception occurs, I do not think
|> > that this is generally the case.  Imagine an exception from operator=
|> > during a sort; I don't think it at all reasonable to require the
|> > implementation to restore the vector or whatever to its initial state
|> > before sort was called.

|> If I have defined a class T which is well-behaved, but which is not
|> copy-safe (it contains some vectors, etc), I would like a gauarantee
|> that sort on a vector< T > with the default allocator will not lose
|> any memory, and will properly destroy all temporaries of type T.

|> > Part of the reason (I suspect) that the current public draft doesn't say
|> > anything about exceptions is that it is not obvious what it should say.
|> > It takes a lot of time and work to analyse, function by function, what
|> > is reasonable, and what is not.  IMHO, for starters, it is reasonable to
|> > say that exiting from a const function or a destructor by an exception
|> > while in the standard library is undefined behavior, and that otherwise,
|> > the library must leave all objects in a coherent state (at least
|> > coherent enough so they can be deleted).

|> This is the goal of defining a well-behaved container, above.  You can't
|> make any such claim if the user has given you a very strange allocator,
|> but you don't want to make no claim at all.  Hence the well-behaved allocator
|> idea -- if I just tweak the allocator a little, I don't lose any gaurantees.

|> > The problems come in the
|> > details: are there exceptions?  (string::c_str is a const function, but
|> > I can easily imagine reasonable implementations of string in which it
|> > could throw a bad_alloc.)  What is meant by "coherent" in a specific
|> > context?  (Think about the "sort" case.  While I think that we all agree
|> > that it is not reasonable for the routine to restore the sequence to its
|> > original order, suppose it originally contained (a,b,c,d) in some order.
|> > Would (a,b,b,d) (in some order) be considered coherent?  This effect can
|> > easily result if operator= throws an exception during a swap, for
|> > example.)

|> If restoration is desired, a copy of the original can be kept, I suppose.

|> > |> 2) Are there any gauarantees at all in the presence of exceptions
|> > |>    thrown by T::T( const T & ), T::T( ), and T::operator = ( const T & )?
|> > |>    (I think I can see how to write implementations which will deal
|> > |>    well with exceptions thrown from T::T( const T & ) and T::T( ),
|> > |>    but I think dealing well with an exception from T::operator = ( const T & )
|> > |>    would cause efficiency problems in the implementation of insert
|> > |>    and remove.)
|> >
|> > There are none that I know of in the standard.  In practice, the HP
|> > implementation of vector, for example, may result in a memory (or other
|> > resource) leak; already constructed objects are *NOT* destructed, and
|> > the memory for the vector is not freed.

|> This is what I want to avoid.

|> > I am curious about your comments concerning operator=.  As far as I can
|> > tell, operator= will not normally even be used in insert and remove.

|> In vector::insert, there is a call to something like copy_backward to move
|> things up in storage and make room for the inserted object.  I presume that
|> the standard implementation of copy_backward uses operator =.

Probably.  I've not looked at it.  The effect of inserting into the
middle of a vector hadn't occured to me.  (I was thinking of things like
push_back, which, of course, use the copy constructor.)

In practice, I think your distinction of "copy safe" is the right one.
Typically, the assignment operator should act like destruction of the
old value, followed by copy construction of the new one.  If we accept
that destructors don't (shouldn't) throw exceptions, then this basically
means that copy constructors and assignment will/will not throw
exceptions under the same circomstances.

|> If the class T
|> is not copy-safe, then it is very difficult to revert to previous state
|> in case of an exception -- insert would have to allocate new storage large
|> enough for the whole list, copy it over with a hole, construct the new
|> element, and then replace the vector's existing storage with this new version.
|> That doesn't seem worth it, and the client code can keep its own copy of the
|> previous state if it needs to.  However, if T is copy-safe, then insert
|> can only fail at the beginning, and things are easier to gaurantee.
|> (All of this in the presence of a well-behaved allocator, of course --
|> if I can't copy around references and pointers, I'm in trouble.)

While I like your idea of copy-safe, we must not forget just how
restrictive it is.  No type which uses dynamic memory and does a deep
copy is copy-safe.

|> > |> Another question related to error recovery:
|> >
|> > |> 4) If a vector< T > has successfully held 50 elements at some point
|> > |>    in the execution of the program, is that vector< T > gauranteed
|> > |>    to still be able to hold 50 elements without throwing a bad_alloc
|> > |>    exception?
|> >
|> > There are some guarantees as to when allocation may take place.  For
|> > example, after executing a vector<T>::reserve( 50 ), it is guaranteed
|> > that no allocation will take place unless the vector becomes larger than
|> > 50 elements.  I don't think that there are any other guarantees, but I
|> > doubt that many implementations will distinguish why they got so big,
|> > and free up memory when they get smaller if reserve has not been called.

|> It would be nice to know something like "after a call to reserve( 50 ),
|> if at any time the size of the vector is less than 50, then no allocation
|> will take place for an insertion, push_back, or assignment unless the vector
|> then becomes larger than 50 elements.

I think that this is guaranteed.

|> This allows some error recovery that is otherwise very difficult:

|>     myvector.reserve( myvector.size( )); // to make sure we can put oldvector back.
|>     vector< T > oldvector = myvector;    // if this throws, we throw.
|>     try {
|>         myvector.resize( 0 );
|>         while( -- count )
|>             myvector.push_back( something );
|>     } catch( ... ) {
|>         myvector = oldvector; // cannot fail even if myvector got big first, then failed.

Cannot fail if vectors are copy-safe.  The current implementation, I
think frees up the old value and then does a deep copy in this case.  It
is not copy-safe.

|>     }

|> In the absense of any gaurantee, I claim that I cannot safely do this operation.

Agreed.

Worse: with only the guarantees in the current draft, you cannot even
destruct the vector, much less restore it to its previous state.  (In
practice, I think that the current implementations always leave the
vector in a destructable state.  They will result in memory leaks,
however, and undestructed member elements.
--
James Kanze         Tel.: (+33) 88 14 49 00        email: kanze@gabi-soft.fr
GABI Software, Sarl., 8 rue des Francs-Bourgeois, F-67000 Strasbourg, France
Conseils,    tudes et r   alisations en logiciel orient    objet --
                -- A la recherche d'une activit    dans une region francophone
---
[ comp.std.c++ is moderated.  To submit articles: Try just posting with your
                newsreader.  If that fails, use mailto:std-c++@ncar.ucar.edu
  comp.std.c++ FAQ: http://reality.sgi.com/austern/std-c++/faq.html
  Moderation policy: http://reality.sgi.com/austern/std-c++/policy.html
  Comments? mailto:std-c++-request@ncar.ucar.edu
]





Author: kanze@lts.sel.alcatel.de (James Kanze US/ESC 60/3/141 #40763)
Date: 1996/09/26
Raw View
In article <324950DA.2180@elastic.avid.com> "Garth A. Dickie"
<dickie@fantasia.avid.com> writes:

|> I have been unable to find much discussion of exception safety
|> in the C++ standard library.  I would like to know what the
|> behavior of, say, vector< T > is in the presence of exceptions.

So would a lot of other people.  There has been some discussion of this
from time to time in this forum, but for the moment, nothing official
from the committee.  The problem definitly wasn't treated in the last
public draft.

|> In particular:

|> 1) If vector<T>::push_back( const T & ) fails with a bad_alloc
|>    exception thrown, what is the precise state of the vector
|>    which was being operated on?  Is it gauranteed to be in the
|>    same state that it was in before the failed push_back call?

I would guess that this is rather safe.  After all, the vector must do
the allocation before it can do anything else:-).

More generally, although I think push_back can be written to
successfully guarantee no change if an exception occurs, I do not think
that this is generally the case.  Imagine an exception from operator=
during a sort; I don't think it at all reasonable to require the
implementation to restore the vector or whatever to its initial state
before sort was called.

Part of the reason (I suspect) that the current public draft doesn't say
anything about exceptions is that it is not obvious what it should say.
It takes a lot of time and work to analyse, function by function, what
is reasonable, and what is not.  IMHO, for starters, it is reasonable to
say that exiting from a const function or a destructor by an exception
while in the standard library is undefined behavior, and that otherwise,
the library must leave all objects in a coherent state (at least
coherent enough so they can be deleted).  The problems come in the
details: are there exceptions?  (string::c_str is a const function, but
I can easily imagine reasonable implementations of string in which it
could throw a bad_alloc.)  What is meant by "coherent" in a specific
context?  (Think about the "sort" case.  While I think that we all agree
that it is not reasonable for the routine to restore the sequence to its
original order, suppose it originally contained (a,b,c,d) in some order.
Would (a,b,b,d) (in some order) be considered coherent?  This effect can
easily result if operator= throws an exception during a swap, for
example.)

|> 2) Are there any gauarantees at all in the presence of exceptions
|>    thrown by T::T( const T & ), T::T( ), and T::operator = ( const T & )?
|>    (I think I can see how to write implementations which will deal
|>    well with exceptions thrown from T::T( const T & ) and T::T( ),
|>    but I think dealing well with an exception from T::operator = ( const T & )
|>    would cause efficiency problems in the implementation of insert
|>    and remove.)

There are none that I know of in the standard.  In practice, the HP
implementation of vector, for example, may result in a memory (or other
resource) leak; already constructed objects are *NOT* destructed, and
the memory for the vector is not freed.

I am curious about your comments concerning operator=.  As far as I can
tell, operator= will not normally even be used in insert and remove.

|> 3) Are there any gauarantees in the presence of a T::operator &( )
|>    which can throw an exception?  (This is only an issue for me
|>    because of a compiler bug -- to destroy a T, code for Visual C++
|>    must use ( & object )->~T( ) instead of object.~T( ), in order
|>    to be able to instantiate vector< someType * >.)

|> Another question related to error recovery:

|> 4) If a vector< T > has successfully held 50 elements at some point
|>    in the execution of the program, is that vector< T > gauranteed
|>    to still be able to hold 50 elements without throwing a bad_alloc
|>    exception?

There are some guarantees as to when allocation may take place.  For
example, after executing a vector<T>::reserve( 50 ), it is guaranteed
that no allocation will take place unless the vector becomes larger than
50 elements.  I don't think that there are any other guarantees, but I
doubt that many implementations will distinguish why they got so big,
and free up memory when they get smaller if reserve has not been called.
--
James Kanze         Tel.: (+33) 88 14 49 00        email: kanze@gabi-soft.fr
GABI Software, Sarl., 8 rue des Francs-Bourgeois, F-67000 Strasbourg, France
Conseils,    tudes et r   alisations en logiciel orient    objet --
                -- A la recherche d'une activit    dans une region francophone



[ comp.std.c++ is moderated.  To submit articles: try just posting with      ]
[ your news-reader.  If that fails, use mailto:std-c++@ncar.ucar.edu         ]
[ FAQ:      http://reality.sgi.com/employees/austern_mti/std-c++/faq.html    ]
[ Policy:   http://reality.sgi.com/employees/austern_mti/std-c++/policy.html ]
[ Comments? mailto:std-c++-request@ncar.ucar.edu                             ]





Author: "Garth A. Dickie" <dickie@fantasia.avid.com>
Date: 1996/09/27
Raw View
James Kanze US/ESC 60/3/141 #40763 wrote:
> |> I have been unable to find much discussion of exception safety
> |> in the C++ standard library.  I would like to know what the
> |> behavior of, say, vector< T > is in the presence of exceptions.
>
> So would a lot of other people.  There has been some discussion of this
> from time to time in this forum, but for the moment, nothing official
> from the committee.  The problem definitly wasn't treated in the last
> public draft.

I have been thinking about it some more, and it seems hard to say anything
meaningful in the presence of non-default allocators (what do you do if
the assignment operator on the const_reference type throws an exception?!?).

It would be relatively easy to lay down some minimal guidelines, but then
people will want to strengthen them to the strongest possible statements,
which is much more difficult.

Here is the level of detail that I would find useful:

    By a "well-behaved class" we mean a class whose destructor never
    throws an exception.  There should probably be a requirement that
    all classes defined in the standard library are well-behaved whenever
    their template arguments and all public or protected types declared
    in those template arguments are well-behaved.  (Exactly which
    types?  It doesn't really matter if a contained class declares
    non-well-behaved classes, but it does if an allocator class has
    a poorly-behaved pointer type.)

    By a "well-behaved allocator" we mean an allocator which throws an
    exception only from the method allocate( size_type ), and such that
    no operations on objects of the types pointer, const_pointer,
    reference, const_reference, size_type, etc may throw an exception.
    (Exactly which operations?)

    Require that the default allocator be well-behaved.

    Require that any container class, when instantiated with a well-behaved
    allocator and containing objects of a well-behaved class, will eventually
    call free on all pointers allocated with allocate.  (This is difficult
    to gaurantee without a well-behaved allocator, since assigning the
    pointer returned by allocate might throw an exception otherwise...)
        This means that the container, in methods which allocate temporary
    memory, must put almost everything after the allocation into a try
    block, catch( ... ), free the temporary memory, and throw;.
    It won't have to worry about exceptions from destroy, since it is
    containing objects of a well-behaved class.
        This seems like the bare minimum that should be true in order for
    the standard library to be useful in robust code; otherwise there
    will be memory leaks in the presence of exceptions.  This is already
    some extra work for implementors; the Modena library of 4/96 does not
    have this property.

    By a "well-behaved iterator" we mean an iterator which does not
    throw an exception from its copy constructor, assignment operator,
    or (of course) destructor.  Is it possible to make the iostream
    iterators well-behaved?

    By a "well-behaved container" we mean a container which, when instantiated
    with a well-behaved allocator and containing objects of a well-behaved
    class, always returns new iterators which are well-behaved, and if
    always passed well-behaved iterators as arguments, always returns
    well-behaved iterators.

    Require that the container classes in the standard library all be
    well-behaved.

    By a "copy-safe" class we mean a class whose copy constructor and operator =
    will never throw an exception.  Observe that the built-in types and all
    POD-classes are copy-safe.

    By a "well-behaved operation" we mean an operation which, when operating
    on well-behaved containers and iterators, instantiated with well-behaved
    classes and allocators, properly destroys all temporary objects created
    during its execution in the presence of exceptions.
    It should also, if the container is instantiated on a copy-safe class, make
    some appropriate gaurantee depending on the nature of the operation --
    for instance, insert might might gaurantee that the container retains its
    original state (if the contained object is not copy-safe, insert might gauarantee
    that all of the original objects remain in the container, with at most one
    appearing twice; but this gets harder to specify);
    sort might gauarantee that all of the original objects remain in the container,
    and that the container has its original size (if the contained object is not
    copy-safe, sort might gaurantee nothing at all...)

    Require that all of the usual operations push_back, pop_front, insert, etc.
    and all of the algorithms provided in the standard library are well-behaved.

> |> 1) If vector<T>::push_back( const T & ) fails with a bad_alloc
> |>    exception thrown, what is the precise state of the vector
> |>    which was being operated on?  Is it gauranteed to be in the
> |>    same state that it was in before the failed push_back call?
>
> I would guess that this is rather safe.  After all, the vector must do
> the allocation before it can do anything else:-).

I think that this could be required of well-behaved containers, but
the requirement that the allocator be well-behaved is neccessary.

> More generally, although I think push_back can be written to
> successfully guarantee no change if an exception occurs, I do not think
> that this is generally the case.  Imagine an exception from operator=
> during a sort; I don't think it at all reasonable to require the
> implementation to restore the vector or whatever to its initial state
> before sort was called.

If I have defined a class T which is well-behaved, but which is not
copy-safe (it contains some vectors, etc), I would like a gauarantee
that sort on a vector< T > with the default allocator will not lose
any memory, and will properly destroy all temporaries of type T.

> Part of the reason (I suspect) that the current public draft doesn't say
> anything about exceptions is that it is not obvious what it should say.
> It takes a lot of time and work to analyse, function by function, what
> is reasonable, and what is not.  IMHO, for starters, it is reasonable to
> say that exiting from a const function or a destructor by an exception
> while in the standard library is undefined behavior, and that otherwise,
> the library must leave all objects in a coherent state (at least
> coherent enough so they can be deleted).

This is the goal of defining a well-behaved container, above.  You can't
make any such claim if the user has given you a very strange allocator,
but you don't want to make no claim at all.  Hence the well-behaved allocator
idea -- if I just tweak the allocator a little, I don't lose any gaurantees.

> The problems come in the
> details: are there exceptions?  (string::c_str is a const function, but
> I can easily imagine reasonable implementations of string in which it
> could throw a bad_alloc.)  What is meant by "coherent" in a specific
> context?  (Think about the "sort" case.  While I think that we all agree
> that it is not reasonable for the routine to restore the sequence to its
> original order, suppose it originally contained (a,b,c,d) in some order.
> Would (a,b,b,d) (in some order) be considered coherent?  This effect can
> easily result if operator= throws an exception during a swap, for
> example.)

If restoration is desired, a copy of the original can be kept, I suppose.

> |> 2) Are there any gauarantees at all in the presence of exceptions
> |>    thrown by T::T( const T & ), T::T( ), and T::operator = ( const T & )?
> |>    (I think I can see how to write implementations which will deal
> |>    well with exceptions thrown from T::T( const T & ) and T::T( ),
> |>    but I think dealing well with an exception from T::operator = ( const T & )
> |>    would cause efficiency problems in the implementation of insert
> |>    and remove.)
>
> There are none that I know of in the standard.  In practice, the HP
> implementation of vector, for example, may result in a memory (or other
> resource) leak; already constructed objects are *NOT* destructed, and
> the memory for the vector is not freed.

This is what I want to avoid.

> I am curious about your comments concerning operator=.  As far as I can
> tell, operator= will not normally even be used in insert and remove.

In vector::insert, there is a call to something like copy_backward to move
things up in storage and make room for the inserted object.  I presume that
the standard implementation of copy_backward uses operator =.  If the class T
is not copy-safe, then it is very difficult to revert to previous state
in case of an exception -- insert would have to allocate new storage large
enough for the whole list, copy it over with a hole, construct the new
element, and then replace the vector's existing storage with this new version.
That doesn't seem worth it, and the client code can keep its own copy of the
previous state if it needs to.  However, if T is copy-safe, then insert
can only fail at the beginning, and things are easier to gaurantee.
(All of this in the presence of a well-behaved allocator, of course --
if I can't copy around references and pointers, I'm in trouble.)

> |> Another question related to error recovery:
>
> |> 4) If a vector< T > has successfully held 50 elements at some point
> |>    in the execution of the program, is that vector< T > gauranteed
> |>    to still be able to hold 50 elements without throwing a bad_alloc
> |>    exception?
>
> There are some guarantees as to when allocation may take place.  For
> example, after executing a vector<T>::reserve( 50 ), it is guaranteed
> that no allocation will take place unless the vector becomes larger than
> 50 elements.  I don't think that there are any other guarantees, but I
> doubt that many implementations will distinguish why they got so big,
> and free up memory when they get smaller if reserve has not been called.

It would be nice to know something like "after a call to reserve( 50 ),
if at any time the size of the vector is less than 50, then no allocation
will take place for an insertion, push_back, or assignment unless the vector
then becomes larger than 50 elements.

This allows some error recovery that is otherwise very difficult:

    myvector.reserve( myvector.size( )); // to make sure we can put oldvector back.
    vector< T > oldvector = myvector;    // if this throws, we throw.
    try {
        myvector.resize( 0 );
        while( -- count )
            myvector.push_back( something );
    } catch( ... ) {
        myvector = oldvector; // cannot fail even if myvector got big first, then failed.
    }

In the absense of any gaurantee, I claim that I cannot safely do this operation.

Regards,
Garth A. Dickie
---
[ comp.std.c++ is moderated.  To submit articles: Try just posting with your
                newsreader.  If that fails, use mailto:std-c++@ncar.ucar.edu
  comp.std.c++ FAQ: http://reality.sgi.com/austern/std-c++/faq.html
  Moderation policy: http://reality.sgi.com/austern/std-c++/policy.html
  Comments? mailto:std-c++-request@ncar.ucar.edu
]





Author: "Garth A. Dickie" <dickie@fantasia.avid.com>
Date: 1996/09/25
Raw View
I have been unable to find much discussion of exception safety
in the C++ standard library.  I would like to know what the
behavior of, say, vector< T > is in the presence of exceptions.

In particular:

1) If vector<T>::push_back( const T & ) fails with a bad_alloc
   exception thrown, what is the precise state of the vector
   which was being operated on?  Is it gauranteed to be in the
   same state that it was in before the failed push_back call?

2) Are there any gauarantees at all in the presence of exceptions
   thrown by T::T( const T & ), T::T( ), and T::operator = ( const T & )?
   (I think I can see how to write implementations which will deal
   well with exceptions thrown from T::T( const T & ) and T::T( ),
   but I think dealing well with an exception from T::operator = ( const T & )
   would cause efficiency problems in the implementation of insert
   and remove.)

3) Are there any gauarantees in the presence of a T::operator &( )
   which can throw an exception?  (This is only an issue for me
   because of a compiler bug -- to destroy a T, code for Visual C++
   must use ( & object )->~T( ) instead of object.~T( ), in order
   to be able to instantiate vector< someType * >.)

Another question related to error recovery:

4) If a vector< T > has successfully held 50 elements at some point
   in the execution of the program, is that vector< T > gauranteed
   to still be able to hold 50 elements without throwing a bad_alloc
   exception?

Thanks for any information you can provide.

Regards,
Garth A. Dickie
---
[ comp.std.c++ is moderated.  To submit articles: Try just posting with your
                newsreader.  If that fails, use mailto:std-c++@ncar.ucar.edu
  comp.std.c++ FAQ: http://reality.sgi.com/austern/std-c++/faq.html
  Moderation policy: http://reality.sgi.com/austern/std-c++/policy.html
  Comments? mailto:std-c++-request@ncar.ucar.edu
]