Google Groups no longer supports new Usenet posts or subscriptions. Historical content remains viewable.
Dismiss

Is self assignment test valid?

32 views
Skip to first unread message

gmr...@o2.pl

unread,
Sep 2, 2008, 12:40:39 AM9/2/08
to
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.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]

Hak...@gmail.com

unread,
Sep 2, 2008, 3:41:32 AM9/2/08
to
On Sep 1, 11:40 pm, gmr...@o2.pl wrote:
> ...in some implementations can be

> stored in CPU registers etc. If it is true, than expression "&rhs"
> would not make any sense.

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.

R.F. Pels

unread,
Sep 2, 2008, 3:44:13 PM9/2/08
to
gmr...@o2.pl wrote:

> -----------------------
> T& T::operator=(T const& rhs) {
> if (&rhs != this)
> // operations
> return *this;
> }
> -----------------------
> Is this example valid and have well defined behavior?

Yes.

> if (&rhs != this)

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

> 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?

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

--
Ruurd

Maciej Sobczak

unread,
Sep 2, 2008, 3:49:06 PM9/2/08
to
On 2 Wrz, 06:40, gmr...@o2.pl wrote:

> -----------------------
> T& T::operator=(T const& rhs) {
> if (&rhs != this)
> // operations
> return *this;}
>
> -----------------------
> Is this example valid and have well defined behavior?

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.

> As far as I know
> rhs can be bound to temporary, which in some implementations can be
> stored in CPU registers etc.

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

Martin T

unread,
Sep 3, 2008, 9:58:43 PM9/3/08
to
Maciej Sobczak wrote:
> On 2 Wrz, 06:40, gmr...@o2.pl wrote:
>> -----------------------
>> Is this example valid and have well defined behavior?
>
> 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).
>
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?

> 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.
>

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

--

Seungbeom Kim

unread,
Sep 4, 2008, 5:29:10 AM9/4/08
to
Martin T wrote:

> Maciej Sobczak wrote:
>>
>> 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).
>>
> 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".

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.

> 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?

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. ;)

>
>> 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.
>>
> Now that's quite a strong assumtion, is it not? "Never happens" is just
> something that's bound to happen sooner or later :-)

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

Ulrich Eckhardt

unread,
Sep 4, 2008, 5:29:10 AM9/4/08
to
Martin T wrote:
> Maciej Sobczak wrote:
> [checking for self-assignment is not sufficient]
> Examples would be nice.

True.

> I remember reading before that self assignemnt protection very often
> hides some flaws, but I really fail to see the "very often".

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);
}


> 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?

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.

>> 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.
>>
> Now that's quite a strong assumtion, is it not? "Never happens" is just
> something that's bound to happen sooner or later :-)

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.

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

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

Uli

Martin T.

unread,
Sep 4, 2008, 3:34:34 PM9/4/08
to
Ulrich Eckhardt wrote:
> Martin T wrote:
>> Maciej Sobczak wrote:
>> [checking for self-assignment is not sufficient]
>> ...

>
> 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.

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_;
};

aku ankka

unread,
Sep 4, 2008, 3:39:28 PM9/4/08
to
On Sep 4, 12:29 pm, Ulrich Eckhardt <dooms...@knuut.de> wrote:
> 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.
>
> > Anyway - we (I) build redundancy into my code so that even with the
> > inevitable bugs it may just continue to run.
>
> It might continue to run, but with incorrect results, if the assignment is
> half-way done.

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.

David Abrahams

unread,
Sep 4, 2008, 6:37:21 PM9/4/08
to

on Thu Sep 04 2008, Ulrich Eckhardt <doomster-AT-knuut.de> wrote:

> 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;
> }
>

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.boostpro.com

Joshua...@gmail.com

unread,
Sep 5, 2008, 12:21:53 AM9/5/08
to
On Sep 4, 3:37 pm, David Abrahams <d...@boostpro.com> wrote:
> on Thu Sep 04 2008, Ulrich Eckhardt <doomster-AT-knuut.de> wrote:
>
> > 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;
> > }
>
> 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 Computinghttp://www.boostpro.com
>
> [ Seehttp://www.gotw.ca/resources/clcm.htmfor info about ]

> [ comp.lang.c++.moderated. First time posters: Do this! ]

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);
};


--

Thomas J. Gritzan

unread,
Sep 5, 2008, 12:21:08 AM9/5/08
to
Martin T. wrote:
[...]

> 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_;

Wrong. Correct is:

delete[] p_;

> }
[...]

One reason never to use new[].

--
Thomas

Andrei Alexandrescu

unread,
Sep 5, 2008, 5:34:06 AM9/5/08
to
Joshua...@gmail.com wrote:
> On Sep 4, 3:37 pm, David Abrahams <d...@boostpro.com> wrote:
>> on Thu Sep 04 2008, Ulrich Eckhardt <doomster-AT-knuut.de> wrote:
>>
>>> 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;
>>> }
>> 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,

{ quoted signature and banner removed -mod }

> 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);
> };

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.erdani.org/publications/cuj-02-2003.html. In short,
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

Joshua...@gmail.com

unread,
Sep 6, 2008, 12:34:25 PM9/6/08
to
On Sep 5, 2:34 am, Andrei Alexandrescu <and...@cs.washington.edu>
wrote:
> Seehttp://www.erdani.org/publications/cuj-02-2003.html. In short,

> 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.

Ok. I see and agree.

> 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.

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.

Andrei Alexandrescu

unread,
Sep 6, 2008, 8:02:50 PM9/6/08
to
Joshua...@gmail.com wrote:
> On Sep 5, 2:34 am, Andrei Alexandrescu <and...@cs.washington.edu>
>> 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.
>
> 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.

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

David Abrahams

unread,
Sep 7, 2008, 7:28:46 AM9/7/08
to

on Sat Sep 06 2008, Andrei Alexandrescu <SeeWebsiteForEmail-AT-erdani.org> wrote:

> Had this rule be in place in the beginning, the entire rvalue reference
> proposal would not have been needed.

Wrong. You still need perfect forwarding.

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

[ See http://www.gotw.ca/resources/clcm.htm for info about ]

Andrei Alexandrescu

unread,
Sep 7, 2008, 12:03:47 PM9/7/08
to
David Abrahams wrote:
> on Sat Sep 06 2008, Andrei Alexandrescu <SeeWebsiteForEmail-AT-erdani.org> wrote:
>
>> Had this rule be in place in the beginning, the entire rvalue reference
>> proposal would not have been needed.
>
> Wrong. You still need perfect forwarding.

That segues into an issue different from copy construction. Namely, the
language design decision causing the lack of perfect forwarding is:
references are not first-class and therefore (naturally) they are not
automatically deduced for lvalues in a template function call.

To clarify:

template <class T> void foo(T a) {}

will only bind T to a non-reference type (one without a trailing "&")
regardless of how it's called. This is a good rule because otherwise
people will get confused that in a call-by-value language they get
pass-by-reference in templates. Yet sometimes there is a need to say,
"if x is an int lvalue bind T = int&. If x is a const int lvalue bind T
to T = const int&. If x is an int rvalue bind T to T = int." (The rvalue
const int case is not necessary in C++ because const is not transitive.)

That particular case could be solved in a number of ways. Assuming that
copy efficiency is taken care of by allowing by-value copy constructors,
the case can be solved without adding a new type by allowing an
"optional reference" as a storage class. For example:

template <class T> void foo(T &? a) {}

indicates that the compiler should generate two foo()s if needed, one
with & and one without.

The remaining problem is, what if foo wants to return one of its
arguments. Here's where auto could come into play to deduce the result
type of the function.

This all would be quite useless "what if" talk, so normally I'd spare
you all of that, but D2 will implement perfect forwarding quite exactly
as outlined above. Which does not make it a much more useful talk to
someone not interested in D, but at least takes the "what if" out :o).

To tie this discussion with the one on max, the implementation of max in
D2 would be:

auto max(T1, T2)(ref? T1 a, ref? T2 b) {
return b > a ? b : a;

Andrei Alexandrescu

unread,
Sep 8, 2008, 9:36:21 AM9/8/08
to
Andrei Alexandrescu wrote:
> David Abrahams wrote:
>> on Sat Sep 06 2008, Andrei Alexandrescu
>> <SeeWebsiteForEmail-AT-erdani.org> wrote:
>>
>>> Had this rule be in place in the beginning, the entire rvalue reference
>>> proposal would not have been needed.
>>
>> Wrong. You still need perfect forwarding.
>
> That segues into an issue different from copy construction. Namely, the
> language design decision causing the lack of perfect forwarding is:
> references are not first-class and therefore (naturally) they are not
> automatically deduced for lvalues in a template function call.
>
> To clarify:
>
> template <class T> void foo(T a) {}
>
> will only bind T to a non-reference type (one without a trailing "&")
> regardless of how it's called.

Before someone else points it out: explicit instantiation can force T to
be a reference type. What I meant was that passing lvalues vs. rvalues
won't make a difference to foo when deducing T.

Niels Dekker - no return address

unread,
Sep 8, 2008, 4:05:56 PM9/8/08
to
Andrei Alexandrescu wrote:

> JoshuaMaurice (at) gm...com wrote:
>> 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);
>> };
>
> 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.

Do you think this is still relevant when operator= is inline, and it's just
doing "T(x).swap(*this)"? If so, I might consider submitting another ticket
regarding boost::function, which still copy-assigns in the "politically
correct" way at the moment. As so many of us do :-)

> See http://www.erdani.org/publications/cuj-02-2003.html

Thanks! Looking a the example of passing the result of MakeUrl() to the
function Connect(const String& url), you wrote: "For a compiler to optimize
away the copy, it has to do the Herculean job of (1) getting access to
Connect's definition (hard with separately compiled modules), (2) parse
Connect's definition to develop an understanding of it, and (3) alter
Connect's behavior so that the temporary is fused with finalUrl."

Now is it still such a Herculean job for a compiler to do so for an /inline/
assignment operator that's only just swapping a temporary copy? Honestly, I
haven't done any profiling on this, so I just hope you did so already :-)

Kind regards,

Niels

--
Niels Dekker
http://www.xs4all.nl/~nd/dekkerware
Scientific programmer at LKEB, Leiden University Medical Center

David Abrahams

unread,
Sep 9, 2008, 3:28:57 AM9/9/08
to

on Mon Sep 08 2008, "Niels Dekker - no return address" <noreply-AT-this.is.invalid> wrote:

> Andrei Alexandrescu wrote:
>> JoshuaMaurice (at) gm...com wrote:
>>> 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);
>>> };
>>
>> 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.
>
> Do you think this is still relevant when operator= is inline, and it's just
> doing "T(x).swap(*this)"?

Absolutely. The compiler is allowed to elide the implicit copy when the
argument is an rvalue if the corresponding parameter is taken by-value.
However, when you write

T(x)

and x is an lvalue (as it is inside the operator= above), the compiler
is required to honor your explicit instruction to make a copy of x.

> If so, I might consider submitting another ticket regarding
> boost::function, which still copy-assigns in the "politically correct"
> way at the moment. As so many of us do :-)

Could you do that for all the other libraries too? I'm serious, I've
been meaning to make a sweep across the source code to fix this.

>> See http://www.erdani.org/publications/cuj-02-2003.html
>
> Thanks! Looking a the example of passing the result of MakeUrl() to the
> function Connect(const String& url), you wrote: "For a compiler to optimize
> away the copy, it has to do the Herculean job of (1) getting access to
> Connect's definition (hard with separately compiled modules), (2) parse
> Connect's definition to develop an understanding of it, and (3) alter
> Connect's behavior so that the temporary is fused with finalUrl."
>
> Now is it still such a Herculean job for a compiler to do so for an /inline/
> assignment operator that's only just swapping a temporary copy? Honestly, I
> haven't done any profiling on this, so I just hope you did so already :-)

Yes, it's still a fairly herculean job. The compiler would have to
prove that the entire program works exactly the same way with or without
the copy, proving that nothing about the address or identity of the new
T is significant. While it's possible that someone might implement that
optimization for some cases where T's copy constructor, swap, and
destructor are all inlined, that level of understanding is not typically
developed in optimizers.

In the case of copy-elision for by-value arguments and return values,
the compiler is explicitly allowed to _assume_ there is no semantic
difference between the original rvalue and its copy. That's low-hanging
fruit for a compiler writer, that pays huge dividends.

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

[ See http://www.gotw.ca/resources/clcm.htm for info about ]

kalman

unread,
Sep 18, 2008, 5:28:55 PM9/18/08
to

Both of you have a point here, what I'm used to using GCC is the usage
of likely/unliley macros
in order to give your compiler a branch prediction information, so you
operator becomes:

#define likely(x) __builtin_expect(!!(x), 1)
#define unlikely(x) __builtin_expect(!!(x), 0)

T& T::operator=(T const& rhs) {

if (likely(&rhs != this))
// operations
return *this;
}

Regards
Gaetano Mendola

Jeff Koftinoff

unread,
Sep 20, 2008, 9:38:32 PM9/20/08
to
On Sep 18, 2:28 pm, kalman <mend...@gmail.com> wrote:
>
> Both of you have a point here, what I'm used to using GCC is the usage
> of likely/unliley macros
> in order to give your compiler a branch prediction information, so you
> operator becomes:
>
> #define likely(x) __builtin_expect(!!(x), 1)
> #define unlikely(x) __builtin_expect(!!(x), 0)
>
> T& T::operator=(T const& rhs) {
> if (likely(&rhs != this))
> // operations
> return *this;
>
> }
>
> Regards
> Gaetano Mendola
>

I know that this is not standard c++ but is there any way to do that
without #define and have it still mean the same thing?


--jeffk++

kalman

unread,
Sep 22, 2008, 8:24:07 PM9/22/08
to
On Sep 2, 9:49 pm, Maciej Sobczak <see.my.homep...@gmail.com> wrote:

> 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.

Would you be able to tell if this is a self assignment:

t[i] = t[k];

or even:

class T;
T t;
T& a = t;
// Later in the code:

t = a;

The rule is simple: "if you write an assignment operator then you need
to check
for a self assignment, if you wrote it then most probably you are
managing memory
allocated dynamically.

Regards
Gaetano Mendola


--

red floyd

unread,
Sep 23, 2008, 9:07:24 PM9/23/08
to
kalman wrote:
> On Sep 2, 9:49 pm, Maciej Sobczak <see.my.homep...@gmail.com> wrote:
>
>> 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.
>
> Would you be able to tell if this is a self assignment:
>
> t[i] = t[k];
>
> or even:
>
> class T;
> T t;
> T& a = t;
> // Later in the code:
>
> t = a;
>
> The rule is simple: "if you write an assignment operator then you need
> to check
> for a self assignment, if you wrote it then most probably you are
> managing memory
> allocated dynamically.
>
>
No, if you use a Copy and Swap paradigm, then you don't need to check
for self-assignment.

Seungbeom Kim

unread,
Sep 24, 2008, 10:59:07 AM9/24/08
to
kalman wrote:
>
> Would you be able to tell if this is a self assignment:
>
> t[i] = t[k];
>
> or even:
>
> class T;
> T t;
> T& a = t;
> // Later in the code:
>
> t = a;
>
> The rule is simple: "if you write an assignment operator then you need
> to check
> for a self assignment, if you wrote it then most probably you are
> managing memory
> allocated dynamically.

What matters is whether a self assignment works correctly, not whether
you check for one. Checking for one is only one of many ways to
correctly implement it, and many posters have shown that you can do it
without checking, probably more efficiently.

--
Seungbeom Kim

Niels Dekker - no return address

unread,
Sep 24, 2008, 11:08:42 AM9/24/08
to
red floyd wrote:
> if you use a Copy and Swap paradigm, then you don't need to check
> for self-assignment.

Unless you think that self-assignment should never throw an exception...

My 2 cents,


--
Niels Dekker
http://www.xs4all.nl/~nd/dekkerware
Scientific programmer at LKEB, Leiden University Medical Center

[ See http://www.gotw.ca/resources/clcm.htm for info about ]

Maciej Sobczak

unread,
Sep 24, 2008, 12:58:59 PM9/24/08
to
On 23 Wrz, 02:24, kalman <mend...@gmail.com> wrote:

> > 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.
>
> Would you be able to tell if this is a self assignment:
>
> t[i] = t[k];

No.

> or even:
>
> class T;
> T t;
> T& a = t;
> // Later in the code:
>
> t = a;

No.

The above are valid theoretical examples.
Can you also show *practical* ones? I mean - extracted from *real*
application code, that you would be also willing to defend in terms of
its design?

Question: what is the percentage of such assignments in the total
number of assignments?

The check for self-assignment imposes some cost that has to be paid in
all cases. If this overhead is higher than the relative fraction of
self-assignments in the program, then it makes no sense.
Note: we assume that the test for self-assignment is not necessary for
correctness, as already discussed.

> The rule is simple: "if you write an assignment operator then you need
> to check
> for a self assignment

No. If you write it properly, then you don't have to do it to
guarantee correctness and performance will not depend on self-
assignments anyway.

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

The C++ Database Access Library: soci.sourceforge.net

Niklas Bäckman

unread,
Sep 25, 2008, 5:56:05 AM9/25/08
to
"Niels Dekker - no return address" <unk...@this.is.invalid> wrote in
news:48da06e1$0$200$e4fe...@news.xs4all.nl:

> red floyd wrote:
>> if you use a Copy and Swap paradigm, then you don't need to check
>> for self-assignment.
>
> Unless you think that self-assignment should never throw an exception...
>
> My 2 cents,


But if you know are are self-assigning in the first place, why do it at
all? And if you don't know then the assurance that there will be no
exception in that case does not seem so useful either.

--

Niels Dekker - no return address

unread,
Sep 25, 2008, 5:40:41 PM9/25/08
to
>> red floyd wrote:
>>> if you use a Copy and Swap paradigm, then you don't need to
>>> check for self-assignment.

>> Unless you think that self-assignment should never throw an
>> exception...

Niklas Bäckman wrote:
> But if you know are are self-assigning in the first place, why do it
> at all? And if you don't know then the assurance that there will be
> no exception in that case does not seem so useful either.

I find it kinda curious that a simple self-assignment can possibly fail
because of limited resources (typically throwing an std::bad_alloc). And I
think it /does/ make sense to avoid having exceptions when they're easily
avoided.

OTOH, I think that generally speaking, the gain from passing the operator=
parameter by-value, when doing copy-and-swap, is much higher, because it
allows the compiler to do copy elision. As was suggested before in this
thread, by Andrei Alexandrescu and David Abrahams.

foo& foo::operator=(foo arg) // by-value
{
arg.swap(*this);
return *this;
}

When passing the operator= parameter by-value, it's no longer possible to do

the self-assignment check, of course...

Kind regards, Niels

Daryle Walker

unread,
Nov 4, 2008, 1:44:27 PM11/4/08
to
On Sep 6, 7:02 pm, Andrei Alexandrescu <SeeWebsiteForEm...@erdani.org>
wrote:

I don't get it; how does using an rvalue magically make the infinite-
regression problem disappear? Is there some sort of copy-elision
going on (and how would that help if so)?

> 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.

[TRUNCATE]

In (my) other words, how did "another" get loaded up? And could you
show a working rvalue example.

Daryle Walker

0 new messages