Re: how to reuse a copy constructor in an operator = function
On Nov 3, 8:27 pm, Kai-Uwe Bux <jkherci...@gmx.net> wrote:
James Kanze wrote:
On Nov 2, 10:10 pm, Kai-Uwe Bux <jkherci...@gmx.net> wrote:
JD wrote:
My associate has written a copy constructor for a class.
Now I need to add an operator = to the class. Is there a
way to do it without change her code (copy constructor) at
all? Your help is much appreciated.
First off, why would adding an assignment operator affect the
copy constructor? In fact, you can very likely implement
assignment in terms of the copy-constructor. There are two
methods:
(a) Copy-swap:
T& operator= ( T const & t ) {
T(t).swap( *this );
return ( *this );
}
You need to implement a swap() method for this one.
(b) Destruct-resurrect:
T& operator= ( T const & t ) {
if ( &t != this ) {
this->T::~T();
new (this) T (t);
}
return ( *this );
}
This method has gotchas and is not exception safe and is
therefore not applicable in all cases (but despite the harsh
words that H. Sutter finds about it, it can be justified
sometimes).
It also causes no end of problems if someone inherits from the
class.
(a) That's one of the gotchas, and
(b) the reason for using
this->T::~T();
instead of
this->~T();
That actually takes care of _many_ problems this method can
cause in the context of inheritance.
I don't think so. First, if the destructor of the T is
non-trivial, you've probably got undefined behavior---I don't
see what else it could be if you destruct a base class without
destructing the derived class. (This is also true if the
destructor of T isn't virtual, of course.) And of course, After
the new, you have an object of the base class type; any use of
it as the derived class it originally was is undefined behavoir
as well.
So far, I only used it in the implementation of a few
smart-pointers. In that case, the self-assignment test can be
improved to test for equality of values, the destructor is
non-virtual, and the resulting assignment is no-throw.
Moreover, there is no reason to inherit from those
smart-pointers, ever.
If 1) you can be sure that no one will ever derive from the
class, 2) you can be sure that the copy constructor cannot
throw, and 3) you can be sure that anyone modifying the code is
aware of these constraints, and will modify the assignment
operator if they cease to hold, then it's safe. I've never
really seen a case where I feel safe concerning 3, and 1 and 2
generally imply a class so simple that it's not worth the effort
using a dubious idiom.
Rule of thumb: generically (and always, when in doubt), go
with (a).
Only if you can implement a no-throw version of swap.
Well, if swap() is no-throw, then (a) is exception safe. But
even with a possibly throwing swap(), it will still do an
assignment. You just don't get the exception safety for the
assignment.
Nor any other benefits.
Exception safety is not the only reason to use (a) (or (b) for
that matter). The main reason for using one of the two
methods above is code-reuse: if the assignment operation is
non-trivial, it is generally much easier to get it right by
defining it in terms of the copy-constructor. E.g., in the
context of reference-counted smart-pointers, it appears to be
somewhat difficult to get the order of count-adjustments
right.
It depends. If I'm writing such a pointer today, I'd define a
nothrow swap, and use it. But long before I was aware of the
idiom, I knew enough to increment the pointer being copied
first, and decrement the one being overwritten second.
Alternatively, you could just check whether the two pointers
pointed to the same thing first.
Given that method (b) requires too much care, I tend to use
(a) in a first implementation of the assignment operator. Any
subsequent change is considered an optimization.
If I'm more or less in control of all of the classes of the
sub-objects, or the sub-objects are not class types, I'll
generally go with (a) too, unless the assignment operator is
really, really trivial. (I generally won't bother, for example,
if the object only contains arithmetic types: integers, floating
point and enums.) I generally won't use either (a) or (b) if
the object contains class type sub-objects which do not
themselves implement a no throw swap. In such cases, I'm
dealing with legacy code, or third party software, and I have to
actually think about what I want to do.
This is not generally the case, and if swap throws somewhere
in the middle, you may end up with an incoherent state.
Right And the same is true for any other non-exception safe
implementation of the assignment operator.
Not necessarily.
--
James Kanze (GABI Software) email:james.kanze@gmail.com
Conseils en informatique orient=E9e objet/
Beratung in objektorientierter Datenverarbeitung
9 place S=E9mard, 78210 St.-Cyr-l'=C9cole, France, +33 (0)1 30 23 00 34