Topic: exception safety question


Author: Matvei Brodski <mbrodski@bear.com>
Date: Mon, 11 Jun 2001 18:28:24 GMT
Raw View
Howard Hinnant wrote:

> In article <3B2005BC.9741654@bear.com>, Matvei Brodski
> <mbrodski@bear.com> wrote:
>
> | Ok, now I see what you mean. Correct me, if I am wrong - the idea
> | is to "move" *internals* of T (assigning built-in members and
> | "move"ing others) - basically, just 1/3 of how I thought swap
> | can be specialized.
> | move needs to be standard then.
>
> Exactly, and then swap (for a type that supports move) can look like:
>
> T tmp(move(a));
> a = move(b);
> b = move(tmp);
>
> Of course a type should still be free to specialize swap if it can do
> better than this.
>
> | imho, adding a no-throwing std::swap for your class can be as good
> | a habit as providing copy constructor, operator= and a destructor
> | (for classes that need them).
>
> Throw in "move" and I agree with you 100%. :-)  I view swap much more
> as a primitive operation of a type (like op= and copy constructor),
> rather than as a std::algorithm.

Not to be outdone: actually, if we were to make some kind of
TypeTraits a standard (along with the "move") we can include
is_moveable in the list of traits and then just use your swap
above to specialize std::swap for all that "moves" (that swap
specialization wont throw).

:o)

Matvei.

---
[ 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    ]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://www.research.att.com/~austern/csc/faq.html                ]





Author: Matvei Brodski <mbrodski@bear.com>
Date: Fri, 8 Jun 2001 05:40:28 GMT
Raw View
Andrei Alexandrescu wrote:

> "Howard Hinnant" <hinnant@antispam.metrowerks.com> wrote in message
> news:060620011702041011%hinnant@antispam.metrowerks.com...
> >I believe that a std::fstream doesn't currently support swap semantics.

> Rare?  A singleton probably should support swap, move or copy.  Rare?
> Boosters considered non-copyable classes pervassive enough that they
> created boost::non_copyable (spelling?).  A non-movable object will
> certainly be common enough.<
>
> I agree. There's a lot of legacy code out there. You can't add swap and move
> semantics to any class that easily.

Absolutely.
If, however, we are talking about some objects that we intend to
"insert" into a vector (as we initially did), then I can assume
those objects can be copied. Which means - generic std::swap will
handle them just fine (it can throw, though, if copy ctor throws).
I can not think of a good reason why it would be impossible to
implement a no-throwing specialization for std::swap for those
classes. It can not be done automatically, of course: one would
have to just put in code for each one of them.

imho, adding a no-throwing std::swap for your class can be as good
a habit as providing copy constructor, operator= and a destructor
(for classes that need them). This also gives you a strong guarantee
for an operator=, if one to use Sutter's idea:
MyClass& operator=( const MyClass& rhs )
{
    MyClass temp( rhs );
    std::swap( *this, temp );
    return *this;
}



> >As luck would have it I recently did a demo implementation of
> vector::push_back on this very subject.<
>
> Do you have numbers?
>
> >Move constructor syntax for object T:
>
> T x;
> T y(std::move(x));<
>
> [snip]
>
> >is_convertible (boost or Loki) might be used to detect if a non-POD has
> a constructor taking a std::move_t<T>.

Ok, now I see what you mean. Correct me, if I am wrong - the idea
is to "move" *internals* of T (assigning built-in members and
"move"ing others) - basically, just 1/3 of how I thought swap
can be specialized.
move needs to be standard then.


> By the way. Be afraid. Be very afraid. Work on YASLI has started.

Should we go check stlport.com?

Matvei.

---
[ 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    ]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://www.research.att.com/~austern/csc/faq.html                ]





Author: Howard Hinnant <hinnant@antispam.metrowerks.com>
Date: Fri, 8 Jun 2001 19:23:42 GMT
Raw View
In article <3B2005BC.9741654@bear.com>, Matvei Brodski
<mbrodski@bear.com> wrote:

| Ok, now I see what you mean. Correct me, if I am wrong - the idea
| is to "move" *internals* of T (assigning built-in members and
| "move"ing others) - basically, just 1/3 of how I thought swap
| can be specialized.
| move needs to be standard then.

Exactly, and then swap (for a type that supports move) can look like:

T tmp(move(a));
a = move(b);
b = move(tmp);

Of course a type should still be free to specialize swap if it can do
better than this.

| imho, adding a no-throwing std::swap for your class can be as good
| a habit as providing copy constructor, operator= and a destructor
| (for classes that need them).

Throw in "move" and I agree with you 100%. :-)  I view swap much more
as a primitive operation of a type (like op= and copy constructor),
rather than as a std::algorithm.

--
Howard Hinnant
Metrowerks

---
[ 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    ]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://www.research.att.com/~austern/csc/faq.html                ]





Author: "Andrei Alexandrescu" <andrewalex@hotmail.com>
Date: Thu, 7 Jun 2001 16:49:57 GMT
Raw View
"Howard Hinnant" <hinnant@antispam.metrowerks.com> wrote in message
news:060620011702041011%hinnant@antispam.metrowerks.com...
>I believe that a std::fstream doesn't currently support swap semantics.
Rare?  A singleton probably should support swap, move or copy.  Rare?
Boosters considered non-copyable classes pervassive enough that they
created boost::non_copyable (spelling?).  A non-movable object will
certainly be common enough.<

I agree. There's a lot of legacy code out there. You can't add swap and move
semantics to any class that easily.

>As luck would have it I recently did a demo implementation of
vector::push_back on this very subject.<

Do you have numbers?

>Move constructor syntax for object T:

T x;
T y(std::move(x));<

[snip]

>is_convertible (boost or Loki) might be used to detect if a non-POD has
a constructor taking a std::move_t<T>.  Another possibility is an error
operator (similar to sizeof) that would return a non-zero if the
expression generated an error, instead of an error message.<

Makes sense. This is similar to what I indend to do, with two twists: (1) I
offer the possibility to move ranges (2) move_t converts implicitly to a T&.

[snip]
>Use of move on some objects should result in a large performance gain,
and memory usage reduction. (thinking vector<string> and
vector<vector<T>>)<

One of YASLI's pillars will be that all containers co-operate efficiently so
you can compound them freely. You will have efficient vectors of strings,
vectors of maps, vectors of vectors of vectors, the wolf and the lamb will
live in harmony...

>Nothing is set in stone.  Not the syntax, not the semantics.  Not even
whether or not move is an idea worthy of standardization.
There is still a lot of work to do to make the above code snippet
become a reality.<

Agree. I think getting a standardized way of moving objects around is
crucial to creating efficient containers. The language could come to help,
but if at least the library defined and used consistently a move primitive
that can be overloaded by users, that could prove very effective, and YASLI
will do precisely that.

By the way. Be afraid. Be very afraid. Work on YASLI has started.


Andrei

--
Check out THE C++ Seminar:  3 Days with 5 Experts
http://www.gotw.ca/cpp_seminar



---
[ 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    ]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://www.research.att.com/~austern/csc/faq.html                ]





Author: Matvei Brodski <mbrodski@bear.com>
Date: Tue, 5 Jun 2001 10:24:15 CST
Raw View
Howard Hinnant wrote:

> | > Or we can move them by calling std::swap. If user provided a no-throw
> | > specialization for std::swap for the containee class (which is a Good
> | > Thing To Do), we are home safe.
> |
> | How can you move the elements by swapping? I have a vector containing
> | elements ABCDE, and I want to add FG after A
> |
> | Copy-construct the extras:
> |
> | ABCDEDE
> |
> | Move the elements
> |
> | ABCBEDE // assign B
> | ABCBCDE // assign C
> | AFCBCDE // assign F
> | AFGBCDE // assign G
> |
> | With swap we could do
> |
> | ADCBEDE // swap D and B
> | ADEBCDE // swap E and C
> |
> | but we still have to assign F and G, as we don't know we can destroy the
> | original.
> |
> | Also, how do we know we have a no-throw swap (or no-throw move as I
> | expressed a wish for)?
>
> An additional problem with using swap when you mean move is that it is
> 3 times the cost of an assign (or move) when the element type is a POD.

> (agreeing that a no-throw move is the way to go)

If the element type is a POD we do not have a problem with the
exceptions in the first place. If we were to have a standard
TypeTraits (like ones in Andrei's Loki), we could have had something
like TypeTraits::TriviallyCopyable for POD types. This would have
allowed a specialization of the insert() that does not throw at all
(if no memory reallocation needed).

How would you implement a no-throw move for a non-POD?

Matvei.

---
[ 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    ]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://www.research.att.com/~austern/csc/faq.html                ]





Author: Howard Hinnant <hinnant@antispam.metrowerks.com>
Date: Tue, 5 Jun 2001 23:12:10 GMT
Raw View
In article <3B1C0F28.CBF37817@bear.com>, Matvei Brodski
<mbrodski@bear.com> wrote:

| > An additional problem with using swap when you mean move is that it is
| > 3 times the cost of an assign (or move) when the element type is a
| POD.
|
| > (agreeing that a no-throw move is the way to go)
|
| If the element type is a POD we do not have a problem with the
| exceptions in the first place. If we were to have a standard
| TypeTraits (like ones in Andrei's Loki), we could have had something
| like TypeTraits::TriviallyCopyable for POD types. This would have
| allowed a specialization of the insert() that does not throw at all
| (if no memory reallocation needed).

My point was that I do not believe the use of swap in vector::insert
would be a good solution because I think it would be too expensive for
a certain class of contained objects (such as POD's).  Although I agree
with you that if POD detection is possible, vector::insert could choose
different algorithms which are appropriate for the type.

| How would you implement a no-throw move for a non-POD?

Depends greatly on the nature of the non-POD.  And I don't believe that
all non-POD's will be able to support move (just as not all non-POD's
support copy semantics).

I believe the no-throw guarantee is central to the definition of move.
If it can throw, it is not a move (by my definition).

As an example, std::list might implement move semantics by calling its
splice method on the rhs.

--
Howard Hinnant
Metrowerks

---
[ 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    ]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://www.research.att.com/~austern/csc/faq.html                ]





Author: Ron Natalie <ron@spamcop.net>
Date: Mon, 4 Jun 2001 18:05:12 GMT
Raw View

Bjarne Stroustrup wrote:

> As I and others have said often enough: The Standard in not a tutorial.
>
Speaking of standards and tutorials, what ever happened to the project you
and Andrew Koenig were working on to put out an annotated version?

---
[ 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    ]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://www.research.att.com/~austern/csc/faq.html                ]





Author: Matvei Brodski <mbrodski@bear.com>
Date: Mon, 4 Jun 2001 19:20:56 GMT
Raw View
Anthony Williams wrote:

> "Matvei Brodski" <mbrodski@bear.com> wrote in message
> news:3B17D5F6.F801161D@bear.com...
> >
> > Anthony Williams wrote:
> >
> > > (3) No reallocation necessary, inserting in the middle
> > >
> > > If we copy-construct the n elements at the end of the vector first, then
> an
> > > exception here can be treated as an insert at the end- destruct the new
> > > elements, ensure size() is correct, all is well.
> > >
> > > Once we have copy-constructed these elements, we move the remainder by
> > > assignment.
> >
> > Or we can move them by calling std::swap. If user provided a no-throw
> > specialization for std::swap for the containee class (which is a Good
> > Thing To Do), we are home safe.
>
> How can you move the elements by swapping? I have a vector containing
> elements ABCDE, and I want to add FG after A
>
> Copy-construct the extras:
>
> ABCDEDE

Wait, I thought you wanted to copy construct like this:

ABCDEFG

I mean - copy the new elements in, not the old ones...
then swap:

F & D : ABCFEDG
F & B : AFCBEDG
G & E : AFCBGDE
G & C : AFGBCDE

If swap doesn't throw, we have the same strong safety
guarantees as in the other 2 cases


> Also, how do we know we have a no-throw swap (or no-throw move as I
> expressed a wish for)?

We don't. But we can change a guarantee from:

"If an exception is thrown other than by the copy constructor
or assignment operator of T there are no effects."

to

"If an exception is thrown other then by swap... " -

which is better, since it is much easier to provide a
no-throwing swap then copy ctor.
Encourages good habits too.

Matvei.

---
[ 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    ]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://www.research.att.com/~austern/csc/faq.html                ]





Author: Howard Hinnant <hinnant@antispam.metrowerks.com>
Date: Mon, 4 Jun 2001 20:04:32 GMT
Raw View
In article <9ffdjb$3pcqs$1@ID-49767.news.dfncis.de>, Anthony Williams
<anthwil@nortelnetworks.com> wrote:

| "Matvei Brodski" <mbrodski@bear.com> wrote in message
| news:3B17D5F6.F801161D@bear.com...
| >
| > Anthony Williams wrote:
| >
| > > (3) No reallocation necessary, inserting in the middle
| > >
| > > If we copy-construct the n elements at the end of the vector first, then
| an
| > > exception here can be treated as an insert at the end- destruct the new
| > > elements, ensure size() is correct, all is well.
| > >
| > > Once we have copy-constructed these elements, we move the remainder by
| > > assignment.
| >
| > Or we can move them by calling std::swap. If user provided a no-throw
| > specialization for std::swap for the containee class (which is a Good
| > Thing To Do), we are home safe.
|
| How can you move the elements by swapping? I have a vector containing
| elements ABCDE, and I want to add FG after A
|
| Copy-construct the extras:
|
| ABCDEDE
|
| Move the elements
|
| ABCBEDE // assign B
| ABCBCDE // assign C
| AFCBCDE // assign F
| AFGBCDE // assign G
|
| With swap we could do
|
| ADCBEDE // swap D and B
| ADEBCDE // swap E and C
|
| but we still have to assign F and G, as we don't know we can destroy the
| original.
|
| Also, how do we know we have a no-throw swap (or no-throw move as I
| expressed a wish for)?

An additional problem with using swap when you mean move is that it is
3 times the cost of an assign (or move) when the element type is a POD.

(agreeing that a no-throw move is the way to go)

--
Howard Hinnant
Metrowerks

---
[ 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    ]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://www.research.att.com/~austern/csc/faq.html                ]





Author: "Anthony Williams" <anthwil@nortelnetworks.com>
Date: Tue, 5 Jun 2001 14:40:09 GMT
Raw View
"Matvei Brodski" <mbrodski@bear.com> wrote in message
news:3B1BDF12.5E3C7350@bear.com...
> Anthony Williams wrote:
>
> > "Matvei Brodski" <mbrodski@bear.com> wrote in message
> > news:3B17D5F6.F801161D@bear.com...
> > >
> > > Anthony Williams wrote:
> > >
> > > > (3) No reallocation necessary, inserting in the middle
> > > >
> > > > If we copy-construct the n elements at the end of the vector first,
then
> > an
> > > > exception here can be treated as an insert at the end- destruct the
new
> > > > elements, ensure size() is correct, all is well.
> > > >
> > > > Once we have copy-constructed these elements, we move the remainder
by
> > > > assignment.
> > >
> > > Or we can move them by calling std::swap. If user provided a no-throw
> > > specialization for std::swap for the containee class (which is a Good
> > > Thing To Do), we are home safe.
> >
> > How can you move the elements by swapping? I have a vector containing
> > elements ABCDE, and I want to add FG after A
> >
> > Copy-construct the extras:
> >
> > ABCDEDE
>
> Wait, I thought you wanted to copy construct like this:
>
> ABCDEFG
>
> I mean - copy the new elements in, not the old ones...
> then swap:
>
> F & D : ABCFEDG
> F & B : AFCBEDG
> G & E : AFCBGDE
> G & C : AFGBCDE

OK, this is a better sequence using swap - I just substituted swap into the
same sequence I had for assignment.

> If swap doesn't throw, we have the same strong safety
> guarantees as in the other 2 cases

Yes.

> > Also, how do we know we have a no-throw swap (or no-throw move as I
> > expressed a wish for)?
>
> We don't. But we can change a guarantee from:
>
> "If an exception is thrown other than by the copy constructor
> or assignment operator of T there are no effects."
>
> to
>
> "If an exception is thrown other then by swap... " -
>
> which is better, since it is much easier to provide a
> no-throwing swap then copy ctor.
> Encourages good habits too.

I agree. I didn't think things through properly before replying. Even a
throwing swap is never any worse than the original situation.

Anthony
--
Anthony Williams
Software Engineer, Nortel Networks Optoelectronics
The opinions expressed in this message are not necessarily those of my
employer



---
[ 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    ]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://www.research.att.com/~austern/csc/faq.html                ]





Author: Matvei Brodski <mbrodski@bear.com>
Date: Wed, 6 Jun 2001 17:47:11 GMT
Raw View
Howard Hinnant wrote:

> In article <3B1C0F28.CBF37817@bear.com>, Matvei Brodski
> <mbrodski@bear.com> wrote:
>
> | > An additional problem with using swap when you mean move is that it is
> | > 3 times the cost of an assign (or move) when the element type is a
> | POD.
> |
> | > (agreeing that a no-throw move is the way to go)
> |
> | If the element type is a POD we do not have a problem with the
> | exceptions in the first place. If we were to have a standard
> | TypeTraits (like ones in Andrei's Loki), we could have had something
> | like TypeTraits::TriviallyCopyable for POD types. This would have
> | allowed a specialization of the insert() that does not throw at all
> | (if no memory reallocation needed).
>
> My point was that I do not believe the use of swap in vector::insert
> would be a good solution because I think it would be too expensive for
> a certain class of contained objects (such as POD's).  Although I agree
> with you that if POD detection is possible, vector::insert could choose
> different algorithms which are appropriate for the type.

POD "detection" is not necessary. Loki gives you the way to detect
built-in types, as for the classes - they should be specifically
designated by their designer as POD (through some type_traits - which
would be a very nice addition to the standard. Wont break any old
code for sure...).

> | How would you implement a no-throw move for a non-POD?
>
> Depends greatly on the nature of the non-POD.  And I don't believe that
> all non-POD's will be able to support move (just as not all non-POD's
> support copy semantics).

non-POD that does not support swap semantics would be a real rarity, wouldn't
it?


> I believe the no-throw guarantee is central to the definition of move.
> If it can throw, it is not a move (by my definition).

Definition is fine. How would you implement it?


> As an example, std::list might implement move semantics by calling its
> splice method on the rhs.

Ok, but we were talking about a vector::insert. We were trying to
implement it. At some point we reached a stage were we needed to
move a block of elements to a new location. We do not have any
information about the type of the element. Can be a non-POD. How
would you implement a no-throw move to use in your vector::insert?

I see two ways:
1) use a cycle of std::swap. We guarantee that if a no-throw
swap specialization for the elements class is provided, the move
does not throw.

2) use some variation of the std::swap_ranges (where both ranges
are from the same container)
template<class Iter>
void swap_ranges( Iter first, Iter last, size_t distance )

Now, user can provide a no-throwing specialization for this one too.
However, it is not a standard function (which is significant.
we should be able say: "vector::insert gives a strong guarantee if
swap_ranges doesn't throw". )
Also, even though it can (for a long ranges of large objects) be up
to three times faster then move that uses swap, it is only a part
of what vecor::insert does.

Matvei.

---
[ 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    ]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://www.research.att.com/~austern/csc/faq.html                ]





Author: Howard Hinnant <hinnant@antispam.metrowerks.com>
Date: Wed, 6 Jun 2001 21:09:28 GMT
Raw View
In article <3B1E6B17.6D305BB3@bear.com>, Matvei Brodski
<mbrodski@bear.com> wrote:

| > Although I agree
| > with you that if POD detection is possible, vector::insert could choo=
se
| > different algorithms which are appropriate for the type.
|=20
| POD "detection" is not necessary. Loki gives you the way to detect
| built-in types, as for the classes - they should be specifically
| designated by their designer as POD (through some type_traits - which
| would be a very nice addition to the standard. Wont break any old
| code for sure...).

<chuckle> I think we are in violent agreement.  By POD detection I
meant Loki, boost::type_traits, existing SGI primitives (I believe
that's where I first heard of it), maybe some future C++ standard,
whatever.

| non-POD that does not support swap semantics would be a real rarity, wo=
uldn't
| it?

I believe that a std::fstream doesn't currently support swap semantics.=20
Rare?  A singleton probably should support swap, move or copy.  Rare?=20
Boosters considered non-copyable classes pervassive enough that they
created boost::non_copyable (spelling?).  A non-movable object will
certainly be common enough.

| Ok, but we were talking about a vector::insert. We were trying to
| implement it. At some point we reached a stage were we needed to
| move a block of elements to a new location. We do not have any
| information about the type of the element. Can be a non-POD. How
| would you implement a no-throw move to use in your vector::insert?
|=20
| I see two ways:
| 1) use a cycle of std::swap. We guarantee that if a no-throw
| swap specialization for the elements class is provided, the move
| does not throw.
|=20
| 2) use some variation of the std::swap_ranges (where both ranges
| are from the same container)
| template<class Iter>
| void swap_ranges( Iter first, Iter last, size_t distance )
|=20
| Now, user can provide a no-throwing specialization for this one too.
| However, it is not a standard function (which is significant.
| we should be able say: "vector::insert gives a strong guarantee if
| swap_ranges doesn't throw". )
| Also, even though it can (for a long ranges of large objects) be up
| to three times faster then move that uses swap, it is only a part
| of what vecor::insert does.

As luck would have it I recently did a demo implementation of
vector::push_back on this very subject.

There are quite a few details that have yet to be worked out.  Indeed,
this is /extremely/ preliminary.  But here's what I've got so far (with
lots of much appreciated help from others - who are free to come in and
argue on the points where we still disagree! :-) ).

Move constructor syntax for object T:

T x;
T y(std::move(x));

Move assignment syntax for object T

y =3D std::move(x);

It is up to the author of T to implement move.  If he does, it must be
a no-throw operation.  The implementation will provide move for POD's
(or at least built-in types) as just simple assignment.

struct MyClass
{
   MyClass(const std::move_t<MyClass>& x) throw()
      {MyClass& other =3D x.get();...}
   MyClass& operator =3D (const std::move_t<MyClass>& x) throw()
      {MyClass& other =3D x.get();...}
};

is_convertible (boost or Loki) might be used to detect if a non-POD has
a constructor taking a std::move_t<T>.  Another possibility is an error
operator (similar to sizeof) that would return a non-zero if the
expression generated an error, instead of an error message.

T x;
T y;
const bool has_no_move_assign =3D error(y =3D std::move(x));

Now vector::push_back can detect the presence of move semantics at
compile time, and choose to use them, or the existing copy semantics.=20
Here is an untested code snippit based on Bjarne's exception safety
appendix:

template<class T, class A =3D allocator<T> >
struct vector_base {
  A alloc; // allocator
  T* v; // start of allocation
  T* space; // end of element sequence
  T* last; // end of allocated space
  vector_base(const A& a, typename A::size_type n)
    : alloc(a) , v(a.allocate(n)) , space(v) , last(v+n) { }
  =97vector_base() { clear(); alloc.deallocate(v,last-v); }
};

// has copy semantics

template <class T, class A, bool NoMove>
struct vector_imp
    : vector_base<T, A>
{
    void push_back(const T& x);
};

template< class T, class A>
void vector_imp<T, A, true>::push_back(const T& x)
{
  if (space =3D=3D last) { // no more free space; relocate:
    vector_base b(alloc, size() ? 2*size() : 2);
    for (T* p =3D v; p < space; ++p, ++b.space)
      new(b.space) T(*p);
    new(b.space) T(x);
    ++b.space;
    swap<vector_base<T,A> >(b,*this);
    return;
  }
  new(space) T(x);
  ++space;
}

// has move semantics

template <class T, class A>
struct vector_imp<T, A, false>
    : vector_base<T, A>
{
    void push_back(const T& x);
};

template< class T, class A>
void vector_imp<T,A, false>::push_back(const T& x)
{
  if (space =3D=3D last) { // no more free space; relocate:
    vector_base b(alloc, size() ? 2*size() : 2);
    new(b.space+size()) T(x);
    for (T* p =3D v; p < space; ++p, ++b.space)
    {
      new(b.space) T(move(*p));
      p->~T();
    }
    ++b.space;
    space =3D v;
    swap<vector_base<T,A> >(b,*this);
    return;
  }
  new(space) T(x);
  ++space;
}

template <class T> T make();

template <class T, class A>
class vector
    : private vector_imp<T, A, error(T x(move(make<T>())))>
{
public:
    using push_back;
};

Notes:

vector::push_back retains the strong exception safety guarantee whether
using move or copy semantics.

Not all classes will be capable of supporting move semantics.  Heck,
none of them do right now.  Therefore move detection is critical.

The no-throw constraint on move semantics is critical to the succesful
utilization of move.

Imho, using swap when move is really what is meant will lead to
performance inefficiencies.

Use of move on some objects should result in a large performance gain,
and memory usage reduction. (thinking vector<string> and
vector<vector<T>>)

Nothing is set in stone.  Not the syntax, not the semantics.  Not even
whether or not move is an idea worthy of standardization.

There is still a lot of work to do to make the above code snippet
become a reality.

--=20
Howard Hinnant
Metrowerks

---
[ 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    ]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://www.research.att.com/~austern/csc/faq.html                ]





Author: "David Abrahams" <abrahams@mediaone.net>
Date: Sun, 3 Jun 2001 00:17:27 GMT
Raw View
"Andrei Iltchenko" <iltchenko@yahoo.com> wrote in message
news:20010601092247.55080.qmail@web12503.mail.yahoo.com...
> Andrei Alexandrescu wrote:

<snip standard text>

> > Well, I'm not sure I understand all that. What if

....

> vector<T>::assign offers nothing more than a basic
> guarantee, even if T's copy constructor and copy
> assignment operator do not throw during the operation.
> No guarantee that the operation is atomic is made.

correct.

> I find the STL exception safety guarantees very clean
> and concise

At the time, I would have liked to have put more explicit text in the
standard, but we were somewhat constrained. Now, I think being parsimonious
with words was a good choice.

> If you find it difficult to make sense of what the
> Standard says on the part of container exception
> safety, you can read Appendix E of Bjarne Stroustrup's
> The C++ Programming Language SE. He placed the
> appendix free of charge on his web-site:
> http://www.research.att.com/~bs/3rd_safe0.html

You can also find some of the rationale for what appeared in the standard in
my paper "Exception-Safety in Generic Components"
(http://www.boost.org/more/generic_exception_safety.html), particularly
footnote 13.

-Dave

---
[ 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    ]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://www.research.att.com/~austern/csc/faq.html                ]





Author: "Anthony Williams" <anthwil@nortelnetworks.com>
Date: Mon, 4 Jun 2001 17:36:14 GMT
Raw View
"Matvei Brodski" <mbrodski@bear.com> wrote in message
news:3B17D5F6.F801161D@bear.com...
>
> Anthony Williams wrote:
>
> > (3) No reallocation necessary, inserting in the middle
> >
> > If we copy-construct the n elements at the end of the vector first, then
an
> > exception here can be treated as an insert at the end- destruct the new
> > elements, ensure size() is correct, all is well.
> >
> > Once we have copy-constructed these elements, we move the remainder by
> > assignment.
>
> Or we can move them by calling std::swap. If user provided a no-throw
> specialization for std::swap for the containee class (which is a Good
> Thing To Do), we are home safe.

How can you move the elements by swapping? I have a vector containing
elements ABCDE, and I want to add FG after A

Copy-construct the extras:

ABCDEDE

Move the elements

ABCBEDE // assign B
ABCBCDE // assign C
AFCBCDE // assign F
AFGBCDE // assign G

With swap we could do

ADCBEDE // swap D and B
ADEBCDE // swap E and C

but we still have to assign F and G, as we don't know we can destroy the
original.

Also, how do we know we have a no-throw swap (or no-throw move as I
expressed a wish for)?

Anthony
--
Anthony Williams
Software Engineer, Nortel Networks Optoelectronics
The opinions expressed in this message are not necessarily those of my
employer



---
[ 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    ]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://www.research.att.com/~austern/csc/faq.html                ]





Author: "Andrei Alexandrescu" <andrewalex@hotmail.com>
Date: Fri, 1 Jun 2001 08:16:38 GMT
Raw View
I never was quite sure what the exception safety level of std::vector is, so
I took a look in the standard.

For example, let's take assign. The standard says (23.2.4.1 para 3):

<<
void assign(size_type n, const T& t);

Effects:
erase(begin(), end());
insert(begin(), n, t);
>>

Then I went to vector::insert and I read:

<<
void insert(iterator position, size_type n, const T& x);
...
Notes: Causes reallocation if the new size is greater than the old capacity.
If no reallocation happens, all the iterators and references before the
insertion point remain valid. If an exception is thrown other than by the
copy constructor or assignment operator of T there are no effects.
>>

Well, I'm not sure I understand all that. What if the copy ctor/assignment
operator of T *does* throw? What state is the vector left in? The wording
seems very unintelligible to me. I guess it's one of those things that are
obvious to everybody except yourself. So, could please someone illuminate me
about that? Thanks.


Andrei

--
Check out THE C++ Seminar:  3 Days with 5 Experts
http://www.gotw.ca/cpp_seminar



---
[ 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    ]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://www.research.att.com/~austern/csc/faq.html                ]





Author: Andrei Iltchenko <iltchenko@yahoo.com>
Date: Fri, 1 Jun 2001 16:33:51 GMT
Raw View
Andrei Alexandrescu wrote:
> I never was quite sure what the exception safety
level of std::vector is, so
> I took a look in the standard.
>
> For example, let's take assign. The standard
says(23.2.4.1 para 3):
>
> <<
> void assign(size_type n, const T& t);
>
> Effects:
> erase(begin(), end());
> insert(begin(), n, t);
> >>
>
> Then I went to vector::insert and I read:
>
> <<
> void insert(iterator position, size_type n, const T&
x);
> ...
> Notes: Causes reallocation if the new size is
greater than the old capacity.
> If no reallocation happens, all the iterators and
references before the
> insertion point remain valid. If an exception is
thrown other than by the
> copy constructor or assignment operator of T there
are no effects.
> >>
>
> Well, I'm not sure I understand all that. What if
the copy ctor/assignment
> operator of T *does* throw? What state is the vector
left in? The wording
> seems very unintelligible to me. I guess it's one of
those things that are
> obvious to everybody except yourself. So, could
please someone illuminate me
> about that? Thanks.

vector<T>::assign offers nothing more than a basic
guarantee, even if T's copy constructor and copy
assignment operator do not throw during the operation.
No guarantee that the operation is atomic is made.

I find the STL exception safety guarantees very clean
and concise, I can't say the same about the IOStreams
part of the standard library though.

If you find it difficult to make sense of what the
Standard says on the part of container exception
safety, you can read Appendix E of Bjarne Stroustrup's
The C++ Programming Language SE. He placed the
appendix free of charge on his web-site:
http://www.research.att.com/~bs/3rd_safe0.html


Cheers,

Andrei Iltchenko.


__________________________________________________
Do You Yahoo!?
Get personalized email addresses from Yahoo! Mail - only $35
a year!  http://personal.mail.yahoo.com/

---
[ 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    ]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://www.research.att.com/~austern/csc/faq.html                ]





Author: "Anthony Williams" <anthwil@nortelnetworks.com>
Date: Fri, 1 Jun 2001 16:34:55 GMT
Raw View
"Andrei Alexandrescu" <andrewalex@hotmail.com> wrote in message
news:9f7fo2$2kfig$1@ID-14036.news.dfncis.de...
> I never was quite sure what the exception safety level of std::vector is,
so
> I took a look in the standard.
>
> For example, let's take assign. The standard says (23.2.4.1 para 3):
>
> <<
> void assign(size_type n, const T& t);
>
> Effects:
> erase(begin(), end());
> insert(begin(), n, t);
> >>
>
> Then I went to vector::insert and I read:
>
> <<
> void insert(iterator position, size_type n, const T& x);
> ...
> Notes: Causes reallocation if the new size is greater than the old
capacity.
> If no reallocation happens, all the iterators and references before the
> insertion point remain valid. If an exception is thrown other than by the
> copy constructor or assignment operator of T there are no effects.
> >>
>
> Well, I'm not sure I understand all that. What if the copy ctor/assignment
> operator of T *does* throw? What state is the vector left in? The wording
> seems very unintelligible to me. I guess it's one of those things that are
> obvious to everybody except yourself. So, could please someone illuminate
me
> about that? Thanks.

I was looking at that very paragraph just the other day, thinking exactly
the same. I came to the conclusion that if the copy ctor or assignment
operator *does* throw, then the contents of the vector may be kaputt. I
would expect that the vector would continue to be valid (as a vector), but
the elements may or may not be OK.

I came up with the following scenarios:

(1) Reallocation is necessary to grow the vector.

If a copy-constructors throws whilst copying to the new memory, destruct the
elements already copied, deallocate the new memory, and leave the vector in
its original state. All is well with the world.

(2) No reallocation is necessary, and inserting at the end of the sequence

If a copy-constructor throws, destruct the elements already added, ensure
the size() is the same as it was originally. All is well with the world.

(3) No reallocation necessary, inserting in the middle

If we copy-construct the n elements at the end of the vector first, then an
exception here can be treated as an insert at the end- destruct the new
elements, ensure size() is correct, all is well.

Once we have copy-constructed these elements, we move the remainder by
assignment. If the assignment operator throws, then we really have no way of
back-tracking, since we must back-track by using the assignment operator,
which may throw.... Ensure that size() remains unchanged, and destruct any
element copy-constructed beyond the initial end of the vector. All is not
well - some elements may have been moved or inserted, some not. However, the
vector is still valid as a whole.

Having a no-throw move operation would be good - we need never worry about
assignment, we can just move the original elements up, copy-construct the
new ones into the right spot, and all is done. If the copy-constructors
fail, just destruct the new ones, and move the original elements back. No
problem.

Indeed, if we have a no-throw move operation, we don't even need an
assignment operator, which means you can put objects that aren't Assignable
into a vector.

Note also, that we cannot use this method if move() can throw, otherwise we
may end up with destructed elements in the middle of our sequence, and no
way of re-constructing them (since the copy-constructor may throw, and
move() may throw).

Anthony
--
Anthony Williams
Software Engineer, Nortel Networks Optoelectronics
The opinions expressed in this message are not necessarily those of my
employer



---
[ 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    ]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://www.research.att.com/~austern/csc/faq.html                ]





Author: Matvei Brodski <mbrodski@bear.com>
Date: Fri, 1 Jun 2001 16:48:21 GMT
Raw View
Andrei Alexandrescu wrote:

> <<
> void insert(iterator position, size_type n, const T& x);
> ...
> Notes: Causes reallocation if the new size is greater than the old capacity.
> If no reallocation happens, all the iterators and references before the
> insertion point remain valid. If an exception is thrown other than by the
> copy constructor or assignment operator of T there are no effects.
> >>
>
> Well, I'm not sure I understand all that. What if the copy ctor/assignment
> operator of T *does* throw? What state is the vector left in? The wording
> seems very unintelligible to me. I guess it's one of those things that are
> obvious to everybody except yourself. So, could please someone illuminate me
> about that? Thanks.

I was thinking this way:

If we need to reallocate:
- allocate chunk of memory (hold in auto_ptr)
- copy the elements before and after insertion range from an old vector
- copy x into insertion range
- swap the newly allocated chunk with the old one (old goes into auto_ptr)

This approach can guarantee that if ANY exception is thrown, there are
no effects (assuming swap does not throw). Again, this works IF we are
to reallocate memory.

However, if we do no reallocation (and so - preserve iterators
and references before the insertion point), we must copy x into the
"old" memory directly. I.e., if we copied some of them and then the
next copy throws, we can not reliably "rollback the transaction".

So, we have to choose: do we want to save those iterators or be
exception-safe (or is it exception-neutral?).
Standard chooses the iterators.

hope this helps.
Matvei.

---
[ 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    ]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://www.research.att.com/~austern/csc/faq.html                ]





Author: Matvei Brodski <mbrodski@bear.com>
Date: Fri, 1 Jun 2001 17:55:45 GMT
Raw View
Anthony Williams wrote:

> (3) No reallocation necessary, inserting in the middle
>
> If we copy-construct the n elements at the end of the vector first, then an
> exception here can be treated as an insert at the end- destruct the new
> elements, ensure size() is correct, all is well.
>
> Once we have copy-constructed these elements, we move the remainder by
> assignment.

Or we can move them by calling std::swap. If user provided a no-throw
specialization for std::swap for the containee class (which is a Good
Thing To Do), we are home safe.

Matvei.



---
[ 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    ]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://www.research.att.com/~austern/csc/faq.html                ]