Are there situations when self-assignment is useful?
Asked Answered
N

3

26

It is commonly known that when implementing an assignment operator one has to protect against self-assignment, at least when the class has non-POD members. Usually it is (or is equivalent to):

Foo& operator=(const Foo& other)
{
  if (&other == this)
     return *this;
  ... // Do copy
}

What were the reasons for not inserting the self-assignment protection automatically? Are there use cases when self-assignment does something non-trivial and practically useful?

Foo& operator=(const Foo& other)
{
  if (&other == this)
  {
    // Do something non-trivial
  }
  else
  {
    // Do copy
  }
  return *this;
}

To summarize the answers and discussion by now

Looks like non-trivial self-assignment can never be really useful. The only option proposed was to put an assert there in order to detect some logical errors. But there are quite legitimate self-assignment cases like a = std::min(a, b), so even this option is highly dubious.

But there are two possible implementations of a trivial self-assignment:

  1. Do nothing if &other == this. Always work, though may have negative performance impact due to an extra branching. But in a user-defined assignment operator the test must be almost always explicitly made.
  2. Copy each member to itself. This is what is done by default. If the members use default assignment operators as well, it may be faster, because doesn't requre an extra branching.

I still don't see why the C++ standard could not guarantee that in a user-defined assignment operator &other != this. If you want no branching, use the default operator. If you are redefining the operator, some test is needed anyway...

Needham answered 26/5, 2019 at 18:27 Comment(12)
This is an interesting question. I would even say that typically self-assignment is an indicator of some sort of logic error and it makes sense to use an assertion or other form of strict check. I can definitely imagine it being harmless, but not useful.Joe
You could always dream up your own semantics for self assignment and make it do whatever you want. Like "if I assign this Troll to itself it means it gets triple health and +2 damage bonus to its attack". Yeah, weird way of doing that, but I don't see why the language should automatically prevent you from doing that if you want to.Colwen
You could make some kind of weird recursion. Though it seems odd.Owner
Making self-assignment a non-trivial operation is like making a < b and b > a return different results.Rutter
Well, the C++ standard library is crazy enough to overload << and >> with effectful operations. Maybe they figured someone might want to do similarly crazy things with =.Anh
@Rutter "making a < b and b > a return different results" - You mean, like in PHP? Where == is not necessarily transitive. 😉Colwen
I would say that any function that silently doesn't do what you asked it to do is a code smell. If you want to prevent self asignment, I would say you should throw an exception.Kennie
The conditional branch may make "check and maybe do nothing" slower than "just do it", so the check should not be done by default.Intense
I can also imagine situations where you want to know how often it happens, perhaps for performance reasons. If the language were to "hide" self-assignments, this would be difficult to analyse.Intense
You might find this to be an interesting read: ericniebler.com/2017/03/31/post-conditions-on-self-moveStallworth
@chris, yes, an instructive reading.Needham
While the self-assignment guard is a common idiom it pessimises the most common case. It's worth making the assignment operator just work even when self assigning.Judsen
H
20

Self-assignment protection is only necessary for types where the code being skipped is dangerous when applied to itself. Consider the case where you have a user-provided assignment operator because each individual object has some kind of identifier, which you don't want to copy. Well, you can "copy" the other values just fine in self-assignment cases. So inserting an invisible self-assignment test is just adding a pointless and potentially costly conditional branch.

So it's not about self-assignment being useful; it's about self-assignment not always needing protection.

Furthermore, C++ generally doesn't like adding code like that to your code without you explicitly asking for it. It's typically done in terms of whole functions, not part of functions. Even destructor calls at the end of blocks are something you asked for when you put the object to be destroyed on the stack.

Harwilll answered 26/5, 2019 at 18:41 Comment(3)
OP was asking about a situation when self assignment would be useful, not about whether self-assignment protection is necessary or when it should be implemented. Also I have to disagree with "Self-assignment protection is only necessary for types where the code being skipped is dangerous when applied to itself." Such a check can be very helpful even if self-assignment itself is completely harmless to catch problems in calling code. Typos like foo[i] = foo[i] instead of foo[i] = foo[j] or similar problems happen ridiculously often.Joe
@VTT: The second sentence of the post is "What was the reasons for not inserting the self-assignment protection automatically?" The principle question only exists because of the assumption that this automatic protection would have been there if there wasn't some utility for the concept of self-assignment. My post counters this assumption, and once countered, the question becomes irrelevant.Harwilll
@VTT "Typos like foo[i] = foo[i] instead of foo[i] = foo[j]" the provided approach does not report the typo so it is unclear how it can help fixing them.Serene
T
19

There are algorithms where it can happen.

  1. You know the lhs and rhs might be the same but it is just simpler to do the assignment than check. E.g., consider a = std::min(a,b); - simpler and perhaps easier to understand than if (a > b) a = b; - now consider more complicated examples of similar things.

  2. You don't know whether lhs and rhs might be the same, because they may have been passed in from somewhere else.

These algorithms where it can happen are not uncommon.

Tenney answered 26/5, 2019 at 18:46 Comment(2)
The question was about a non-trivial self-assignment.Needham
I'd say the possibly handled self assignment makes case (1) more complicated, because we first need to branch when comparing a and b and then possibly branch when checking for self assignment instead of just a single branching.Joe
S
-1

I should admit I have never heard about the common knowledge like this. For a non-POD objects, a more strict approach is to forbid copying them with disabling copy constructor and the assignment operator. So that you don't have the problem at all.

If you still need to copy the class, but there is some data which is unsafe to copy to itself, you could only override assignment for that data, and when it is used as a field, it would be used by an automatic assignment implementation of the upper level class.

As for your question, if you only need to skip doing anything the automatic "self-assignment protection" is there already, in a way. If you don't define assignment operation explicitely and let compiler use the automatic one, after inlining self-assignment may become no-op.

For example, the following code:

class A {
    int a;
    double b;
};

A& foo(A& input)
{
    return (input = input);
}

is compiled to (gcc 4.9, -O2):

_Z3fooR1A:
    .cfi_startproc
    movq    %rdi, %rax
    ret
    .cfi_endproc

Which does not copy anything.

Serene answered 27/5, 2019 at 3:39 Comment(2)
"a more strict approach is to forbid copying them with disabling copy constructor and the assignment operator." And what about moving? Note that the OP was not specific about the form of self-assignment: copy or move. While lots of more complex types can't reasonably be copied, few are the types that cannot logically be moved.Harwilll
@NicolBolas the OP's code uses copying, and I meant there copying. I guess for moving next paragraphs apply as well.Serene

© 2022 - 2024 — McMap. All rights reserved.