why a member "swap" function shouldn't throw?

J

Jess

Hello,

It is said that if I implement a "swap" member function, then it
should never throw any exception. However, if I implement "swap" non-
member function, then the restriction doesn't apply. Can somebody tell
me why?

Thanks,
Jess
 
D

Daniel T.

Jess said:
It is said that if I implement a "swap" member function, then it
should never throw any exception. However, if I implement "swap" non-
member function, then the restriction doesn't apply. Can somebody tell
me why?

I think the restriction should apply with non-member functions as well.
 
D

dasjotre

Hello,

It is said that if I implement a "swap" member function, then it
should never throw any exception. However, if I implement "swap" non-
member function, then the restriction doesn't apply. Can somebody tell
me why?

I think that the restriction must apply regardless to whether it is
member or not.

consider

struct A
{
T1 t1_;
T2 t2_;
};

void swap(A & l, A & r)
{
swap(l.t1, r.t1);
swap(l.t2, r.t2);
}

and second swap throws, then you have
half swapped objects.

regards

DS
 
E

Eberhard Schefold

dasjotre said:
void swap(A & l, A & r)
{
swap(l.t1, r.t1);
swap(l.t2, r.t2);
}

and second swap throws, then you have
half swapped objects.

But what is the alternative? Suppressing any errors and have an
erroneous state without a chance of knowing?
 
P

ppi

I think that the restriction must apply regardless to whether it is
member or not.

consider

struct A
{
T1 t1_;
T2 t2_;

};

void swap(A & l, A & r)
{
swap(l.t1, r.t1);
swap(l.t2, r.t2);

}

and second swap throws, then you have
half swapped objects.

regards

DS

swap members should not throw, since most of the times these are the
method used to build a correct operator=(). If swap throws it's
impossible to get operator=() to work properly.
I think H.Sutter wrote about it in one of its books (dunno if you can
find someting in gotw though):

T& operator=( const T& src )
{
T temp(src); // can throw
this->swap( temp );// PLZ do not throw !
return *this;// will not throw
}

If swap throws, we end-up with an ill-formed object, its members state
is unknown.
if the construction of the temp throws it's ok, operator= failed, but
the object is in a clean state - nothing happened.

swap defined outside classes have other purposes, they can carry
business logic with them, they are allowed to throw most of the time.

They do have have the same name, but their purpose are slightly
different.
Hope this will help.

Cheers,
paulo
 
P

Puppet_Sock

On Jun 15, 10:44 am, Eberhard Schefold
[snip]
But what is the alternative? Suppressing any errors and have an
erroneous state without a chance of knowing?

I think it should probably be "commit or roll back"
not "no throws." That is to say, the code should
be constructed such that it can't leave the data
in an unpredictable state. It should either work
correctly or leave the data in the state it was.

For tiny objects being swapped you can be fairly
confident that you won't get an error. Except maybe
a stack problem if you happened to be *right* at the
edge and put one more automatic on there.

But I'm not sure you *can* promise no throws when
you are talking about larger objects being swapped.
Consider, just as an example, you may be required
to do an assign of one object to another to do the
swap. And that might call new, and that might get
you to the end of the freestore. Or fail on some
other resource.
Socks
 
D

dasjotre

But what is the alternative? Suppressing any errors and have an
erroneous state without a chance of knowing?

if you mean try {}catch(...){ do nothing } I agree completely.
it is 'no solution'

you could use PIMPL, swap then becomes
just a swap of pointers and I can not think
of a case when that failed ;)

regards

DS
 
R

Roland Pibinger

It is said that if I implement a "swap" member function, then it
should never throw any exception.

This may be a recommendation but it's not a fixed requirement. You can
use the exception specification throw() to guarantee that no exception
is thrown from a function.
However, if I implement "swap" non-
member function, then the restriction doesn't apply. Can somebody tell
me why?

You probably mean a specialization for std::swap() here. AFAIK, there
are no restrictions WRT exceptions for std::swap().
 
D

Daniel T.

Eberhard Schefold said:
But what is the alternative? Suppressing any errors and have an
erroneous state without a chance of knowing?

There is no reason for a swap to fail (other than truly incredible
circumstances that can't be detected before hand in any case.)
 
D

Daniel T.

Puppet_Sock said:
On Jun 15, 10:44 am, Eberhard Schefold

I think it should probably be "commit or roll back" not "no throws."
That is to say, the code should be constructed such that it can't
leave the data in an unpredictable state. It should either work
correctly or leave the data in the state it was.

For tiny objects being swapped you can be fairly confident that you
won't get an error. Except maybe a stack problem if you happened to
be *right* at the edge and put one more automatic on there.

But I'm not sure you *can* promise no throws when you are talking
about larger objects being swapped.

Larger objects are just collections of tiny objects. There is no
reason for a large object swap to fail either.
Consider, just as an example, you may be required to do an assign of
one object to another to do the swap.

Maybe an example would help?
 
A

Andre Kostur

(e-mail address removed) (Roland Pibinger) wrote in @news.utanet.at:
This may be a recommendation but it's not a fixed requirement. You can
use the exception specification throw() to guarantee that no exception
is thrown from a function.

Um. No. throw() says that you don't intend for exceptions to be
thrown/escape from that function... but in reality, should an exception end
up attempting to escape from that function, all that will do it convert
that exception into a std::bad_exception. So specifying throw() doesn't
guarantee that there will be no exceptions in that function.
 
P

Pete Becker

Andre said:
(e-mail address removed) (Roland Pibinger) wrote in @news.utanet.at:


Um. No. throw() says that you don't intend for exceptions to be
thrown/escape from that function... but in reality, should an exception end
up attempting to escape from that function, all that will do it convert
that exception into a std::bad_exception. So specifying throw() doesn't
guarantee that there will be no exceptions in that function.

You won't ever get any exception whose type is not listed in the throw
specifier.

If an exception is thrown that doesn't match the throw specifier, the
implementation calls unexpected(). The default behavior of unexpected()
is to call terminate().

Your application can replace the unexpected handler by calling
set_unexpected with the address of a suitable handler function. The
handler function isn't allowed to return. It can call terminate(), it
can throw another exception object, or it can rethrow the object that
was thrown. If it throws an exception whose type is one of the types
listed in the throw specifier, then unwinding continues. If it throws an
exception whose type is not listed in the throw specifier AND the list
of types in the throw specifier includes std::bad_exception, the thrown
exception object is replaced by an object of type std::bad_exception. If
the list of types in the throw specifier does not include
std::bad_exception, the implementation calls terminate().

The details are in [except.unexpected]. They're summarized in the third
paragraph of that section:

Thus, an exception-specification guarantees that only
the listed exceptions will be thrown. If the
exception-specification includes the type std::bad_exception
then any exception not on the list may be replaced by
std::bad_exception within the function std::unexpected().

--

-- Pete
Roundhouse Consulting, Ltd. (www.versatilecoding.com)
Author of "The Standard C++ Library Extensions: a Tutorial and
Reference." (www.petebecker.com/tr1book)
 
P

Puppet_Sock

Larger objects are just collections of tiny objects. There is no
reason for a large object swap to fail either.

Um. I'm not altogether sure I agree with the intent of that.
An object with a file handle isn't *just* a collection of
smaller objects. An object with a pointer to memory that
the object allocated isn't *just* a collection of smaller
objects. Both of these are also "managers" of other data.
That data may or may not get copied if the object is copied,
may (probably) or may not get swapped if the objects are.
Maybe an example would help?

Consider a swap based on the tired old temp object.

// fill in the function header stuff as usual
myClass tmp; // 1
tmp = a; // 2
a = b; // 3
b = tmp; // 4

The "function header stuff" could, in principle,
blow the stack. Don't know if a try-catch can
solve that little problem, but let's move on.

Line 1 could involve calls to new, and so could fail.
It's not likely to fail for built in things like ints.
Though it could blow the stack if you were right at
the edge. I seem to recall saying that. Again, I don't
know what a try-catch would do for you in that case.

But if myClass's default ctor involves a call to new,
it could easily blow the freestore. Or, if some other
resource is involved, that could throw. Say it needs a
temp file or something. Maybe the system runs out of
file handles, or the disk is full, or the network times
out or some silly thing.

Line 2 might also involve calls to new, or allocating
resources. Maybe myClass's default ctor just allocates
some stub or something, then the copy does a deep
copy. So now, tmp has to have a copy of all of a's
resources. That might blow the freestore.

Line 3, same thing, particularly if b is lots bigger
than a. And line 4, again, same thing, particularly if
a is lots bigger than b.

I don't think it's possible to do these operations in
such a way that it's not possible for a throw. It is
possible to do them, with some effort, such that there
won't be an unpredictable data state. Either the swap
will work, or an exception gets thrown and the data
goes back to where it was before the operation started.
Socks
 
D

Daniel T.

Puppet_Sock said:
Um. I'm not altogether sure I agree with the intent of that.
An object with a file handle isn't *just* a collection of
smaller objects.

Well no, in that case it is a simple pointer, swapping them is a
no-brainer.
An object with a pointer to memory that
the object allocated isn't *just* a collection of smaller
objects.

Again, you are simply swapping two pointers.
Maybe an example would help?

Consider a swap based on the tired old temp object.

// fill in the function header stuff as usual
myClass tmp; // 1
tmp = a; // 2
a = b; // 3
b = tmp; // 4

The "function header stuff" could, in principle,
blow the stack. Don't know if a try-catch can
solve that little problem, but let's move on.

Line 1 could involve calls to new, and so could fail. [...]
Line 2 might also involve calls to new, or allocating
resources. [...]
Line 3, same thing, particularly if b is lots bigger
than a.

Then provide a swap member-function that doesn't do that. Someone else
has already provided a link to the GotW article that shows that, in
principle, any class can provide an exception safe swap function. This
is old ground.
 
A

Andre Kostur

Andre said:
(e-mail address removed) (Roland Pibinger) wrote in @news.utanet.at:


Um. No. throw() says that you don't intend for exceptions to be
thrown/escape from that function... but in reality, should an
exception end up attempting to escape from that function, all that
will do it convert that exception into a std::bad_exception. So
specifying throw() doesn't guarantee that there will be no exceptions
in that function.

You won't ever get any exception whose type is not listed in the throw
specifier.

If an exception is thrown that doesn't match the throw specifier, the
implementation calls unexpected(). The default behavior of
unexpected() is to call terminate().

Your application can replace the unexpected handler by calling
set_unexpected with the address of a suitable handler function. The
handler function isn't allowed to return. It can call terminate(), it
can throw another exception object, or it can rethrow the object that
was thrown. If it throws an exception whose type is one of the types
listed in the throw specifier, then unwinding continues. If it throws
an exception whose type is not listed in the throw specifier AND the
list of types in the throw specifier includes std::bad_exception, the
thrown exception object is replaced by an object of type
std::bad_exception. If the list of types in the throw specifier does
not include std::bad_exception, the implementation calls terminate().

The details are in [except.unexpected]. They're summarized in the
third paragraph of that section:

Thus, an exception-specification guarantees that only
the listed exceptions will be thrown. If the
exception-specification includes the type std::bad_exception
then any exception not on the list may be replaced by
std::bad_exception within the function std::unexpected().


Apparently my memory is bad. I thought it was always translated to
std::bad_exception.
 
J

James Kanze

It is said that if I implement a "swap" member function, then it
should never throw any exception. However, if I implement "swap" non-
member function, then the restriction doesn't apply. Can somebody tell
me why?

More convention that anything else. A non-member function can
usually not to much else but to use the conventional algorithm:

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

This involves constructing a new object (the tmp), which can
often throw. A member function, however, has access to the
underlying types: pointers, etc., and can usually avoid deep
copy by just swapping the pointers. This is not only
significantly faster in some cases, but swapping two pointers,
even with the above algorithm, will not throw.

Being able to swap without throwing means that we can
effectively copy without throwing: if I have something like:

T x, y ;
x.swap( y ) ;

has the same effect on x as copying y into x, except that a real
copy might throw (since it would deep copy all of y---here that
is avoided by allowing y to become a copy of x, effectively
swapping ownership of any resources, rather than allocating them
new).

This is in turn very useful for things like operator=. Consider
an object that uses the compilation firewall idiom:

// Classical op=
T&
T::eek:perator=( T const& rhs )
{
if ( this != &rhs ) {
delete pImpl ;
pImpl = new Impl( *rhs.pImpl ) ;
// What happens if the above line throws???
}
return *this ;
}

// Using swap.
T&
T::eek:perator=( T const& rhs )
{
T tmp( rhs ) ; // Actual copy, and possible throw,
// occurs here.
std::swap( tmp.pImpl, pImpl ) ;
// Now this is the actual copy, and
// tmp holds all our old resources.
return *this ; // Destructs tmp, thus freeing our
// old resources.
}

Of course, this can be done without swap; I could have just
copied the impl into a temporary pointer, deleted my pImpl, and
then assigned it with the results of the copy. (Which is still
the way I write something as simple as the compilation firewall
idiom.) Doing it this way becomes a bit more complicated,
however, when I have more members, all of which need the same
treatment. And since I'm actually swapping, why not make the
function available at the public interface level? That way, a
class which doesn't use the compilation firewall idiom, but uses
an instance of my class, can easily implement its own no-throw
version of swap. The benefit propagates.

Note, however, that all of this concerning swap is just a means
to an end, and in no way necessary. The important thing isn't
swap; it's having an assignment operator that doesn't leave the
object in an undefined state if copying something in it throws.
Having a no-throw version of swap is just a convenient and
conventional method of ensuring that this is posssible, and in
fact rather easy.
 
J

James Kanze

On Jun 15, 10:44 am, Eberhard Schefold
[snip]
But what is the alternative? Suppressing any errors and have an
erroneous state without a chance of knowing?
I think it should probably be "commit or roll back"
not "no throws." That is to say, the code should
be constructed such that it can't leave the data
in an unpredictable state. It should either work
correctly or leave the data in the state it was.

Those last two statements say different things, and the last is
often more than you need. Depending on the application, a
simple guarantee that the object can be destructed may be
enough. But even that's not obvious unless you are careful.
For tiny objects being swapped you can be fairly
confident that you won't get an error. Except maybe
a stack problem if you happened to be *right* at the
edge and put one more automatic on there.
But I'm not sure you *can* promise no throws when
you are talking about larger objects being swapped.

The whole point of having a member function is that it can swap
the resources, rather than reallocating them to make a deep
copy, as a non-member would have to. When I use the swap idiom
in an assignment operator, for example, I use std::swap for the
built in types, and a member function swap for class types. If
any of the types involved is a class without a member function
swap, I presume that it cannot be swapped without exceptions,
and that the classical swap assignment operator cannot be used.
Consider, just as an example, you may be required
to do an assign of one object to another to do the
swap. And that might call new, and that might get
you to the end of the freestore. Or fail on some
other resource.

It's always possible to write a no throw swap if you control all
of the classes involved. A member swap function swaps all of
the elements, using a global swap for non class types (for which
the generic std::swap cannot throw), and a member swap for class
types (which is written in the same vein). Recursively, we
always end up with built-in types (maybe pointers), which can be
swapped without throwing.
 
J

James Kanze

[...]
Then provide a swap member-function that doesn't do that. Someone else
has already provided a link to the GotW article that shows that, in
principle, any class can provide an exception safe swap function. This
is old ground.

The statement that "any class can provide an exception safe swap
function" isn't true. The statement should be that any class
which consists of only non-class types or class types which
provide an exception safe swap function can provide an exception
safe swap function. If you control all of the classes involved,
this is not a problem; if you encounter an element of a class
type which doesn't provide an exception safe swap function, you
add it to that class, ad infitum, until you reach non class
types. If you don't have that control, i.e. you are using third
party libraries which you cannot modify, it may not be possible
to provide a no throw swap function. And in the same way that
the possibility can propagate through your code, the
impossibility propagates up.
 
D

Daniel T.

James Kanze said:
The statement that "any class can provide an exception safe swap
function" isn't true.

Any class can, in principle, be written using the pimpl idiom, and a
class using the pimpl idiom can always provide an exception safe swap.
The statement should be that any class
which consists of only non-class types or class types which
provide an exception safe swap function can provide an exception
safe swap function. If you control all of the classes involved,
this is not a problem;

You don't need to control all the classes involved... simply write your
class using a single pointer as a member. That way your class will
consist "of only non-class types".
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

Forum statistics

Threads
473,769
Messages
2,569,580
Members
45,055
Latest member
SlimSparkKetoACVReview

Latest Threads

Top