C++ FAQ Lite 12.3

S

stf

http://www.parashift.com/c++-faq-lite/assignment-operators.html#faq-12.3

Fred& Fred::eek:perator= (const Fred& f)
{
// This code gracefully (albeit implicitly) handles self assignment
Wilma* tmp = new Wilma(*f.p_); // It would be OK if an exception
got thrown here
delete p_;
p_ = tmp;
return *this;
}

What if an exception was thrown in

delete p_;

? (It could, because this statement involves calling the destructor of
Wilma, right?) Would the memory region pointed to by p_ remain
allocated? (Here I assume Wilma could be more complicated than in the
example.)

Probably a better solution would be:

Wilma* tmp = p_;
p_ = NULL;
delete tmp;
tmp = new Wilma(*f.p_);
p_ = tmp;
return *this;

?

Thanks,
STF
 
A

Alf P. Steinbach

* stf:
http://www.parashift.com/c++-faq-lite/assignment-operators.html#faq-12.3

Fred& Fred::eek:perator= (const Fred& f)
{
// This code gracefully (albeit implicitly) handles self assignment
Wilma* tmp = new Wilma(*f.p_); // It would be OK if an exception
got thrown here
delete p_;
p_ = tmp;
return *this;
}

What if an exception was thrown in

delete p_;

? (It could, because this statement involves calling the destructor of
Wilma, right?) Would the memory region pointed to by p_ remain
allocated? (Here I assume Wilma could be more complicated than in the
example.)

To be safe, keeping the current code structure, there should be a try-catch
block, yes.

However, destructors should simply not throw (in general).

Probably a better solution would be:

Wilma* tmp = p_;
p_ = NULL;
delete tmp;

You really don't want to modify the current object before you have acquired the
resources to construct the new state.

tmp = new Wilma(*f.p_);
p_ = tmp;
return *this;

A generally better solution is the swap idiom, expressing assignment in terms of
construction instead of the other way.

void Fred::swap( Fred& other ) throw()
{
std::swap( p_, other.p_ );
}

Fred& Fred::eek:perator( Fred other )
{
swap( other );
}

However there are cases where the swap idiom isn't necessesarily most natural or
practical or efficient.

The code above assumes that swapping is essentially cost-free, as it is when the
real state is just a pointer to something dynamically allocated.


Cheers & hth.,

- Alf
 
A

Alf P. Steinbach

* Victor Bazarov:
Did you mean

operator=
Yes.


? And I understand that 'swap' would need a modifiable 'other' but do
you really think that making a copy (to pass by value) is a good idea?

Yes, that's what's operator= is all about, making a copy.

And here we maketh it in the simplest and safest way possible, by copy
construction from actual arg.

Perhaps you just missed the whole point... The issue is with
self-assignment. Essentially, the whole idiom of checking against
self-assignment is replaced in the FAQ case with reallocation and actual
assignment, hoping it doesn't happen often.

The swap idiom tackles self assignment just fine. ;-)

You simply wrap (hide) that
into making a copy upon calling the assignment operator, right? Wouldn't
you have to deal with the OP's 'throw' issue in the copy constructor, then?

Nope.

With a swap based assignment any exception from the destruction of the old state
happens after the assignment has completed.

That exception has to propagate no matter what one does, but the swap based
assignment doesn't leak: since the assignment has gone through it now has a safe
encapsulation of the newly allocated state, instead of leaking it.


Cheers,

- Alf
 
G

gw7rib

What if an exception was thrown in

    delete p_;

I know very little about exceptions, but hopefully this is one I can
answer.

When an exception is thrown, the program leaves what it was doing. If
it is jumping out of a function, any local variables in that function
get destroyed, and in the case of objects, their destructors are
called. An exception may be thrown from a place within several layers
of nested functions, so there may be several lots of variables to be
cleaned up.

Now, if calling one of these destructors throws another exceptuion,
where does the program go next? To deal with this latest exception or
continue trying to deal with the first one? To resolve this, you
shouldn't throw an exception in a destructor. That way, there is only
one exception at a time to be dealt with. Victor and Alf both said
this, though neither seemed to spell out exactly why.

Hope that helps (and indeed that it is accurate!)
Paul.
 
D

Daniel Pitts

I know very little about exceptions, but hopefully this is one I can
answer.

When an exception is thrown, the program leaves what it was doing. If
it is jumping out of a function, any local variables in that function
get destroyed, and in the case of objects, their destructors are
called. An exception may be thrown from a place within several layers
of nested functions, so there may be several lots of variables to be
cleaned up.

Now, if calling one of these destructors throws another exceptuion,
where does the program go next? To deal with this latest exception or
continue trying to deal with the first one? To resolve this, you
shouldn't throw an exception in a destructor. That way, there is only
one exception at a time to be dealt with. Victor and Alf both said
this, though neither seemed to spell out exactly why.

Hope that helps (and indeed that it is accurate!)
Paul.
That is more or less the exact reasoning given in Effective C++
Item 8.
 
J

James Kanze

If the destructor throws, then you get what you deserve.
Generally speaking, destructors should *never* throw.
Why do you need two assignments here?

He doesn't, since the assignment to tmp (p_) can only occur
after the allocation and construction of the Wilma has finished.

On the other hand, in the case of self assignment, there's going
to be a serious problem.
So, how is it better? You attempt to assign, the exception is
thrown, the object is still left in a funky state, and the
function would leak, just like the one in the FAQ.

You mean if the destructor throws.

It's hard to assign any reasonable semantics to code if the
destructor throws. It can be a useful idiom in some very rare,
very special cases, but certainly not in the general case. Once
the compiler enters the destructor, it considers the object
"dead", for all intents and purposes; if the destructor throws,
it's not going to call it again, so you end up with an object
"half destructed". And it will free the memory anyway.
(Actually, I'm not sure whether it will free the memory or not
if the destructor is called as part of a delete expression. I
can't find anything in the standard which says one way or
another. Maybe it's undefined behavior if the destructor in a
delete expression throws.

And one last nit: I think it's pretty clear what we're talking
about, but of course, there's no problem with the destructor
throwing if the exception is caught within the destructor. The
only problem is if the destructor terminates because of the
exception.
 
J

James Kanze

[...]
Did you mean

? And I understand that 'swap' would need a modifiable
'other' but do you really think that making a copy (to pass by
value) is a good idea?

I think he just messed up in typing the example. His code isn't
the standard swap idiom. You can only swap modifiable objects.

This really should be:

Fred tmp( *other ) ;
swap( tmp ) ;
return *this ;

You make the copy, then swap state with it; clean-up is done by
the destructor of the copy (which of course now has your
original state).

Note that as far as is possible, this idiom will even work if
the destructor throws. On the other hand, it requires extra
work (the swap function). Sometimes, it's worth it; other
times, the variant from the FAQ (posted in the original posting
in this thread) may be preferred.

The swap idiom has the advantage of providing a strong exception
safety guarantee---either the operation fully succeeds, or the
object's value is completely unchanged---even if the object has
e.g. several pointers. Typically, however, you don't need the
strong guarantee, and objects with several pointers need
sub-objects to handle each one anyway, for exception safety in
the constructor. Still, the idiom is simple enough and robust
enough that I use it by default as soon as an object has
resources and more than one member.

It also assumes (the case here) that swap can't throw. That's a
very, very important criterion in order to use the swap idiom:
that you can provide a swap function that cannot throw. If your
object contains other objects which don't provide a no-throw
swap, you probably can't.
Perhaps you just missed the whole point... The issue is with
self-assignment. Essentially, the whole idiom of checking
against self-assignment is replaced in the FAQ case with
reallocation and actual assignment, hoping it doesn't happen
often. You simply wrap (hide) that into making a copy upon
calling the assignment operator, right? Wouldn't you have to
deal with the OP's 'throw' issue in the copy constructor,
then?

You missed an important part of the point. If you implement the
code something like:

if ( this != &other ) {
delete p_ ;
p_ = new Wilma( *other.p_ ) ;
}

, then there's no problem with self assignment. But if the
constructor of Wilma throws, you're going to have serious
problems when the destructor of the object (or any other member
function, for that matter) is called.

Alf's right in his analysis, and his explinations. He just got
screwed up in the example code (not enough coffee?).
 
A

Alf P. Steinbach

* James Kanze:
Alf said:
[...]
A generally better solution is the swap idiom, expressing
assignment in terms of construction instead of the other
way.
void Fred::swap( Fred& other ) throw()
{
std::swap( p_, other.p_ );
}
Fred& Fred::eek:perator( Fred other )
Did you mean

? And I understand that 'swap' would need a modifiable
'other' but do you really think that making a copy (to pass by
value) is a good idea?

I think he just messed up in typing the example. His code isn't
the standard swap idiom. You can only swap modifiable objects.

This really should be:

Fred tmp( *other ) ;
swap( tmp ) ;
return *this ;

I think your first '*' is a typo.

And no, except for my typo (missing '=') I meant what I wrote, like

Fred& Fred::swap( Fred other ) { swap( other ); }

'other' is passed by value and is a modifiable object.

Alternatively 'other' can be passed by reference to const, and then a
declaration of a temporary is needed, but it's just more to write.


[snip]
It also assumes (the case here) that swap can't throw.

Yes, that's what the 'throw()' means.

That's a
very, very important criterion in order to use the swap idiom:
that you can provide a swap function that cannot throw. If your
object contains other objects which don't provide a no-throw
swap, you probably can't.


You missed an important part of the point. If you implement the
code something like:

if ( this != &other ) {
delete p_ ;
p_ = new Wilma( *other.p_ ) ;
}

, then there's no problem with self assignment. But if the
constructor of Wilma throws, you're going to have serious
problems when the destructor of the object (or any other member
function, for that matter) is called.

Alf's right in his analysis, and his explinations. He just got
screwed up in the example code (not enough coffee?).

:)

Well, a missing '=' isn't all that much, I think.


Cheers,

- Alf
 
A

Alf P. Steinbach

* Alf P. Steinbach:
And no, except for my typo (missing '=') I meant what I wrote, like

Fred& Fred::swap( Fred other ) { swap( other ); }

Of course, except my for my typo here (writing 'swap' instead of 'operator=') I
meant what I wrote. :)

So, yet again, let's try to get that down without typos:


Fred& Fred::eek:perator=( Fred other ) { swap( other ); }


Now, stay!


Cheers,

- Alf
 
A

Alf P. Steinbach

* Alf P. Steinbach:
* Alf P. Steinbach:

Of course, except my for my typo here (writing 'swap' instead of
'operator=') I meant what I wrote. :)

So, yet again, let's try to get that down without typos:


Fred& Fred::eek:perator=( Fred other ) { swap( other ); }


Now, stay!

And add a 'return' (jeez, it's Friday the 13th).

Cheers,

- Alf
 
T

Triple-DES

[...]
I think he just messed up in typing the example.  His code isn't
the standard swap idiom.  You can only swap modifiable objects.

[...]


This really should be:

    Fred tmp( *other ) ;
    swap( tmp ) ;
    return *this ;

Well, that's one possible version. Like this:

Fred& Fred::eek:perator=(const Fred& other)
{
Fred tmp( other );
swap( tmp );
return *this;
}

But note that Alf's version is equivalent.
// i prefer to name the param tmp, to show that pass-by-value is
intentional
Fred& Fred::eek:perator=(Fred tmp)
{
swap(tmp);
return *this;
}
 
N

Noah Roberts

Alf said:
* stf:

To be safe, keeping the current code structure, there should be a
try-catch block, yes.

Why? What would it give you? You can't try to delete p_ again. Your
program's in a permanently undefined state (how far into the destruction
process did it get? You can't know). You could, I suppose, delete the
tmp object but 'this' object, and really your whole program, is now hosed.

The whole point behind the rule, "Never let the destructor throw," is
that if it does, ALL exception safety goes out the window. You can't
even have the basic guarantee if a destructor throws. No client of any
object that does this can be exception safe, nor can their clients. I
would say that this is one of those things you can go ahead and assume
will never happen, since if it does all bets are off anyway.
> However, destructors should simply not throw (in general).

I would modify that statement by removing the "in general". If
something within a destructor can throw you must catch it and deal with
it there. Either that or tell the compiler not to enable exceptions,
but then you're not using std C++ anymore and certainly not the std lib.

I was going to mention the swap method but you already did. As a
supplement I would mention that the swap method in conjunction with the
pimpl (handle/body) idiom make writing exception safe assignment ops
very easy.
 
A

Alf P. Steinbach

* Noah Roberts:
Why? What would it give you?

For that you'd have to consider the context of the question, namely the code

Fred& Fred::eek:perator= (const Fred& f)
{
Wilma* tmp = new Wilma(*f.p_);
delete p_;
p_ = tmp;
return *this;
}

Here if the delete operation throws and you don't deal with it locally you'll be
propagating an exception with the *this object in a corrupted state.

So to be utterly safe it should be coded like

Fred& Fred::eek:perator= (const Fred& f)
{
Wilma* tmp = new Wilma(*f.p_);
try
{
delete p_;
p_ = tmp;
return *this;
}
catch( ... )
{
abort();
}
}

You can't try to delete p_ again.

Right (at least not without additional machinery in place); that's why, if the
destructor(s) can throw, a try-catch is recommended -- by me, at least. ;-)

Your
program's in a permanently undefined state (how far into the destruction
process did it get? You can't know). You could, I suppose, delete the
tmp object but 'this' object, and really your whole program, is now hosed.
Right.


The whole point behind the rule, "Never let the destructor throw," is
that if it does, ALL exception safety goes out the window. You can't
even have the basic guarantee if a destructor throws. No client of any
object that does this can be exception safe, nor can their clients. I
would say that this is one of those things you can go ahead and assume
will never happen, since if it does all bets are off anyway.

No, I think it's ungood to have that defaitist attitude. You really don't want
to propagate that exception. The higher level code will then, by Murphy's law,
really wreac havoc, at the least convenient time of course.

I would modify that statement by removing the "in general". If
something within a destructor can throw you must catch it and deal with
it there. Either that or tell the compiler not to enable exceptions,
but then you're not using std C++ anymore and certainly not the std lib.

Within the current language and with current compilers I'd mainly agree with that.

However, in principle there are, at least potentially, good uses for throwing
destructors.

For example, Andrei once outlined a scheme for automatic throwing if a
transaction didn't succeed. The problem was, and is, to detect whether the
object with throwing destructor goes out of scope due to a normal return or due
to an exception. With current compilers that can't, in practice, be assured.
This problem has fostered some suggestions that a destructor should have a bool
or other argument telling it the cause of its invocation. But that idea never
got much support.

I was going to mention the swap method but you already did. As a
supplement I would mention that the swap method in conjunction with the
pimpl (handle/body) idiom make writing exception safe assignment ops
very easy.

Uh, not sure what you mean (I have an infestation of nasal demons, a.k.a. the
flu, even if it is version Light(TM)), but letting the object just carry a
pointer to the state certainly makes it easier to guarantee a constant time
non-throwing swap, which in turn makes it real easy to write a safe assignment
op, although not maximally efficient for the case of self assignment.


Cheers & hth.,

- Alf
 
N

Noah Roberts

Alf said:
* Noah Roberts:

For that you'd have to consider the context of the question, namely the
code

Fred& Fred::eek:perator= (const Fred& f)
{
Wilma* tmp = new Wilma(*f.p_);
delete p_;
p_ = tmp;
return *this;
}

Here if the delete operation throws and you don't deal with it locally
you'll be propagating an exception with the *this object in a corrupted
state.

So to be utterly safe it should be coded like

Fred& Fred::eek:perator= (const Fred& f)
{
Wilma* tmp = new Wilma(*f.p_);
try
{
delete p_;
p_ = tmp;
return *this;
}
catch( ... )
{
abort();

I see what you're getting at. Terminate the program. It's a good idea
but I still think it's overkill. No-throw destructors should be
guaranteed and you could get the same behavior as this try/catch if you
simply specify throw() for your destructor. This may or may not slow
down certain code depending on the compiler.

I would concede that you are technically right in personally
guaranteeing through the try/catch (or throw()) but I think that
practically speaking we can let this one go. The likelihood of a
destructor throw in any code should be 0 and in most cases will be.
Careful scrutiny of production code wrt destructors and careful
selection of third party libraries should protect you.
 
J

James Kanze

* James Kanze:
[...]
A generally better solution is the swap idiom, expressing
assignment in terms of construction instead of the other
way.
void Fred::swap( Fred& other ) throw()
{
std::swap( p_, other.p_ );
}
Fred& Fred::eek:perator( Fred other )
Did you mean
operator=
? And I understand that 'swap' would need a modifiable
'other' but do you really think that making a copy (to pass
by value) is a good idea?
I think he just messed up in typing the example. His code
isn't the standard swap idiom. You can only swap modifiable
objects.
{
swap( other );
This really should be:
Fred tmp( *other ) ;
swap( tmp ) ;
return *this ;
I think your first '*' is a typo.
Yup.

And no, except for my typo (missing '=') I meant what I wrote,
like
Fred& Fred::swap( Fred other ) { swap( other ); }
'other' is passed by value and is a modifiable object.

Aha. I'd missed the pass by value. It's not very idiomatic,
and will confuse programmers (which IMHO are good reasons not to
use it), but it will work.

And you're still missing a return:).
[snip]
Alf's right in his analysis, and his explinations. He just
got screwed up in the example code (not enough coffee?).

I could probably do with more coffee too, since I didn't see the
pass by value:).
Well, a missing '=' isn't all that much, I think.

Plus missing the return statement in a function which has a
non-void return type. But all compilers should catch the first,
good compilers will warn about the second, and the correction is
obvious in both case.
 

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

No members online now.

Forum statistics

Threads
473,969
Messages
2,570,161
Members
46,705
Latest member
Stefkari24

Latest Threads

Top