Is self assignment test valid?

Is self assignment test valid?

Post by gmrtm » Wed, 03 Sep 2008 07:40:39


Hello.

Popular example of assignment operator looks like the one below:
-----------------------
T& T::operator=(T const& rhs) {
if (&rhs != this)
// operations
return *this;
}
-----------------------
Is this example valid and have well defined behavior? As far as I know
rhs can be bound to temporary, which in some implementations can be
stored in CPU registers etc. If it is true, than expression "&rhs"
would not make any sense. If it is valid, than could I ask for some
explainations?

Thanks
Sorry for my bad english and using Google Groups

--
[ See http://www.yqcomputer.com/ ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
 
 
 

Is self assignment test valid?

Post by Hakus » Wed, 03 Sep 2008 10:41:32


Implementation can not effect expectation. Compilers are free to
implement the standard library any way they want, but it has to work
as expected. This is no different. You may expect to take the address
of any object.

As far as I know, compilers are smart enough to know when an argument
is not passed through the registers, and I've actually never seen it.
I've looked at the assembly GCC produced and always see arguments
being added to the stack, even of basic types like int. Although,
there is the register keyword, I haven't tested it much.


--
[ See http://www.yqcomputer.com/ ]
[ comp.lang.c++.moderated. First time posters: Do this! ]

 
 
 

Is self assignment test valid?

Post by R.F. Pel » Wed, 03 Sep 2008 22:44:13


Yes.


This tests if the address of rhs is not the same as the address of the
object you're assigning to.


Well, if rhs is bound to a temporary, it is a temporary reference to an
object. But it still references the same object.

--
Ruurd


[ See http://www.yqcomputer.com/ ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
 
 
 

Is self assignment test valid?

Post by Maciej Sob » Wed, 03 Sep 2008 22:49:06


Yes (provided that "operations" make sense), but it is debatable
whether it is a good idiom.

The argument goes that if the reliability of the assignment operator
depends on the existence of the self-assignment-test, then the
operator most likely has some deeper design problems.
It is impossible to assert this for the code above, since the crucial
part ("operations") is not shown, but in practice this is very often
true - if you need this test, then you just try to protect something
that is inherently broken and even this protection is usually not
enough (hint: exception safety).

Performance optimization is the only potentially valid motivation for
such a test, but it actually improves the performance of a use case
that never happens (why assign to same object?) and therefore it does
not make any sense to optimize it.


It does not matter. You can take the address of every named object
(rhs is such an object) and the compiler will do the right thing.

--
Maciej Sobczak * www.msobczak.com * www.inspirel.com


[ See http://www.yqcomputer.com/ ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
 
 
 

Is self assignment test valid?

Post by Martin » Fri, 05 Sep 2008 04:58:43


Examples would be nice.

I remember reading before that self assignemnt protection very often
hides some flaws, but I really fail to see the "very often".
For any object where it makes sense to use e.g. a memcpy in the
copy-ctor the self assignment test would tend to make the copying code
simpler and not only faster, no?

Now that's quite a strong assumtion, is it not? "Never happens" is just
something that's bound to happen sooner or later :-)

Anyway - we (I) build redundancy into my code so that even with the
inevitable bugs it may just continue to run.

br,
Martin

--
[ See http://www.yqcomputer.com/ ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
 
 
 

Is self assignment test valid?

Post by Seungbeom » Fri, 05 Sep 2008 12:29:10


I think this pattern happens often enough:

MyClass& MyClass::operator=(const MyClass& other)
{
if (&other != this) {
// destroy what *this has
// construct in *this a copy of what other has
}
return *this;
}

Guess what happens if the "copy" part throws an exception.


No simpler because of the additional test. Faster only in the rare
case of self-assignment; the additional test actually gives you a
penalty in most cases where the addresses are different.

Moreover, if a simple memcpy is enough, you don't need to define the
copy constructor or the assignment operator, anyway. ;)


Not that we don't have to defend against self-assignment; just that
there's a better way that works in all cases and doesn't need the check
that gives you the penalty in most cases.

The standard idiom for the previous example goes like the following:

MyClass& MyClass::operator=(const MyClass& other)
{
// construct in local variables a copy of what other has
// swap *this and the local variables
}

--
Seungbeom Kim

[ See http://www.yqcomputer.com/ ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
 
 
 

Is self assignment test valid?

Post by Ulrich Eck » Fri, 05 Sep 2008 12:29:10


True.


Simple thing:

struct foo
{
string bar;
string baz;
};

If you assign to this structure, it could happen that the first string is
assigned and during the assignment of the second one a memory shortage
causes a bad_alloc exception. This leads to you ending up with a
half-assigned struct, which will usually mean an inconsistent state.
Replace 'string' and 'memory shortage' above with 'resource' and 'resource
shortage' to make it more general.

I guess the alternative that Maciej was thinking about (but never actually
mentioned) was the copy and swap approach:

operator=( T const& other)
{
T tmp(other); // make a copy
swap( *this, tmp);
return *this;
}

This first allocates necessary resources by making a copy and then swapping
with the target. However, this only works when swap() is implemented in a
way that it will never throw. Unfortunately, std::swap() isn't, because it
simply makes a copy and then uses assignment:

swap( T& t1, T& t2)
{
T tmp = t1;
t1 = t2;
t2 = tmp;
}

However, I think you can safely use swap on types of the standard library
(due to specialisations), or at least use the swap() memberfunction of most
types, so you can build your own swap based on it:

swap( foo& f1, foo& f2)
{
f1.bar.swap(f2.bar);
f1.baz.swap(f2.baz);
}



Well, how much faster? Did you profile it? Actually, I would check for
self-assignment out of habit, but using the copy and swap idiom is more
important, because it gives you some guarantees that you can't achieve
otherwise.


How often do you assign an object to itself? Is that worth optimising it for
speed? I'd say no. In any case though, an there the reason is the 'sooner
or later', I would always try to make it work correctly.


It might continue to run, but with incorrect results, if the assignment is
half-way done.

Uli


--
[ See http://www.yqcomputer.com/ ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
 
 
 

Is self assignment test valid?

Post by Martin T » Fri, 05 Sep 2008 22:34:34


Yes, I know the swap approach but have never used it so far.

I guess there are two ways to think of assignment/copy construction and
that's what got me a little bit confused in the first place.

a) - write a copy-ctor
- implement the operator= in terms of the copy-ctor (copy construct
temp + swap)
Copyable& operator=(Copyable const& o) {
Copyable tmp(o);
this->swap(tmp);
return *this;
}

b) - write operator=
- implement the copy-ctor in terms of operator= (default initialize
this and then assign)
Copyable2& operator=(Copyable2 const& o)
{
if(this == &o)
return *this;
clear();
p_ = new int[o.s_];
s_ = o.s_;
memcpy(p_, o.p_, s_*sizeof(int));
return *this;
}


If you use b), then a test for self assignment makes sense.
I think (a) makes MUCH more sense, but I have also seen (b) quite a few
times.

So I would like to say that if the code "needs" a check for
self-assignment, the the copy operator and copy constructor have been
written "the wrong way round" ...
... and, well, yes - the code is kind of broken if an exception occurs :-)

cheers,
Martin

EXAMPLE CODE:
class Copyable {
public:
Copyable()
: p_(NULL),
s_(0)
{ }

explicit Copyable(size_t s)
: p_(NULL),
s_(0)
{
p_ = new int[s];
s_ = s;
}

~Copyable()
{
delete p_;
}

void swap(Copyable & o)
{
int* p = o.p_;
size_t s = o.s_;
o.p_ = p_;
o.s_ = s_;
p_ = p;
s_ = s;
}

Copyable(Copyable const& o)
{
p_ = new int[o.s_];
s_ = o.s_;
memcpy(p_, o.p_, s_*sizeof(int));
}

Copyable& operator=(Copyable const& o)
{
Copyable tmp(o);
this->swap(tmp);
return *this;
}
private:
int* p_;
size_t s_;
};

class Copyable2 {
public:
Copyable2()
: p_(NULL),
s_(0)
{ }

explicit Copyable2(size_t s)
: p_(NULL),
s_(0)
{
p_ = new int[s];
s_ = s;
}

~Copyable2()
{
delete p_;
}

void clear()
{
delete p_;
p_ = NULL;
s_ = 0;
}

void swap(Copyable2 & o)
{
int* p = o.p_;
size_t s = o.s_;
o.p_ = p_;
o.s_ = s_;
p_ = p;
s_ = s;
}

Copyable2(Copyable2 const& o)
: p_(NULL),
s_(0)
{
*this = o;
}

Copyable2& operator=(Copyable2 const& o)
{
if(this == &o)
return *this;
clear();
p_ = new int[o.s_];
s_ = o.s_;
memcpy(p_, o.p_, s_*sizeof(int));
return *this;
}

private:
int* p_;
size_t s_;
};

--
[ See http://www.yqcomputer.com/ ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
 
 
 

Is self assignment test valid?

Post by aku ankk » Fri, 05 Sep 2008 22:39:28


Still, checking for assingment-to-self as *optimization* isn't a very
good one. It gives penalty to ALL cases and benefit only to a FEW
(rare) cases. Statistically, bad choise.

You can write assignment-to-self correctly w/o knowing this. Example;

struct string
{
char* s;
int length;
};

Wrong:

... operator = (const string& x)
delete[] s;
s = new char[x.length];
...

Right:

char* toDelete = s;
s = new char[x.length];
...
delete[] toDelete;

Problem: creating a temporary object, usually 1 extra ALU instruction.
But better than branch misprediction.



--
[ See http://www.yqcomputer.com/ ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
 
 
 

Is self assignment test valid?

Post by David Abra » Sat, 06 Sep 2008 01:37:21


That's the wrong way to write it, though ;-). On real compilers

operator=( T other )
{
swap( *this, other);
return *this;
}

can be *much* more efficient because of copy elision.

Cheers,

--
Dave Abrahams
BoostPro Computing
http://www.yqcomputer.com/

[ See http://www.yqcomputer.com/ ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
 
 
 

Is self assignment test valid?

Post by Thomas J. » Sat, 06 Sep 2008 07:21:08


[...]

Wrong. Correct is:

delete[] p_;

[...]

One reason never to use new[].

--
Thomas

[ See http://www.yqcomputer.com/ ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
 
 
 

Is self assignment test valid?

Post by JoshuaMaur » Sat, 06 Sep 2008 07:21:53


I just reviewed the standard to make sure I didn't forget something,
but I'm totally not seeing what you're talking about David Abrahams. I
see exactly copy constructor call in both your example code and in
Ulrich Eckhardt's code. The single copy constructor cannot be elided
because the temporary needs to be created to be swapped with *this.


As an aside, the way I've generally done it is as follows, which I
assume to be equivalent to the two previous examples.

struct T
{
T& operator= (T const& x)
{
T(x).swap(*this);
return *this;
}
void swap(T& x);
};


--
[ See http://www.yqcomputer.com/ ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
 
 
 

Is self assignment test valid?

Post by Andrei Ale » Sat, 06 Sep 2008 12:34:06


{ quoted signature and banner removed -mod }


That's the politically correct version. It turns out that, when a
function needs to make a copy of its argument, it better takes it by
value in the first place. That way, if an rvalue is passed in, the
compiler can directly fuse the rvalue to the parameter thus saving a copy.

See http://www.yqcomputer.com/ ,
again, whenever a function plans to make a copy of its argument, it is
correct and recommendable to just have the function take the argument by
value. That includes the assignment operator. I even managed to convince
Herb to introduce that rather unusual (at that time) recommendation in
the C++ Coding Standards book.

Alas, that rule cannot engulf the copy constructor. I believe that's a
rather gratuitous limitation. Many, many, very many things would have
been simpler had that limitation not been in place, sigh.


Andrei

--
[ See http://www.yqcomputer.com/ ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
 
 
 

Is self assignment test valid?

Post by JoshuaMaur » Sun, 07 Sep 2008 19:34:25

On Sep 5, 2:34 am, Andrei Alexandrescu < XXXX@XXXXX.COM >





Ok. I see and agree.


Now, this makes no sense. What is your alternative, a copy constructor
that takes arguments by value? But how is that argument object
constructed? A copy constructor must take its argument by reference.

There has to be some code somewhere which defines how to copy your
object. That code must take its input by reference as it cannot take
the input by value. That code is the copy constructor.

--
[ See http://www.yqcomputer.com/ ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
 
 
 

Is self assignment test valid?

Post by Andrei Ale » Mon, 08 Sep 2008 03:02:50


I'm very glad somebody answered, because I think this is an interesting
subject :o).

The argument you are making is a form of infinite regression, e.g. if
your copy constructor takes a copy by value, then how do you construct
that copy, and how that that copy construct its argument... ad
infinitum. It is the same as the justification of the original copy
constructor design.

I think the argument is fallacious. The regression does not occur when
an rvalue is at the origin. So the only need is to allow creation of an
rvalue, after which the by-value copy constructor can operate on it.
True, a by-value copy constructor could not copy lvalues, but that is
not a fatal limitation, it is just a useful limitation that the original
design was not aware of. Furthermore, overloading can properly take care
of efficient construction from both lvalues and rvalues.

Does auto_ptr sound familiar? :o) Consider a new rule in which by-value
copy constructor is allowed with the semantics that it only accepts
rvalues. Then:

template <class T> class auto_ptr
{
T * p;
public:
auto_ptr(auto_ptr another) { p = another.p; another.p = 0; }
auto_ptr(T * ptr) { p = ptr; }
...
};

Now let's give it a test drive:

auto_ptr<int> a(new int); // fine, copy ctor from int
auto_ptr<int> b = a; // error! nonexisting auto_ptr(auto_ptr&)

In other words, the semantics is quite what auto_ptr tried to attain
with its odd implementation. There are a few simple rules to add, such
as automatically transforming an lvalue into an rvalue upon returning a
local from a function.

Had this rule be in place in the beginning, the entire rvalue reference
proposal would not have been needed. As it turns out, the language has
since made enough turns to make the rule above introduce issues with
existing programs (Howard Hinnant explained me, I forgot the details but
it has something to do with overloading).

Now say you want to define a value type that is initializable with both
rvalues and lvalues. Then you'd define:

struct Val
{
Val(Val rvalue) { ... use the rvalue as you wish ... }
Val(const Val& lvalue) { ... copy lvalue state ... }
...
};

You can also distinguish const lvalues from non-const lvalues if that's
needed, too.


Andrei


--
[ See http://www.yqcomputer.com/ ]
[ comp.lang.c++.moderated. First time posters: Do this! ]