1

First and foremost, there's a similar popular post What is the copy-and-swap idiom?. The accepted answer has a link to https://web.archive.org/web/20140113221447/http://cpp-next.com/archive/2009/08/want-speed-pass-by-value/.

Both the accepted and the linked page state that a commonly used implementation of a copy assignment operator is (copy and pasted from the previous link)

T& T::operator=(T const& x) // x is a reference to the source
{ 
    T tmp(x);          // copy construction of tmp does the hard work
    swap(*this, tmp);  // trade our resources for tmp's
    return *this;      // our (old) resources get destroyed with tmp 
}

but that

T& operator=(T x)    // x is a copy of the source; hard work already done
{
    swap(*this, x);  // trade our resources for x's
    return *this;    // our (old) resources get destroyed with x
}

is better due to copy elision optimizations by the compiler, or in general, always pass by value instead of pass by reference and then copying the parameter passed by reference.

I agree with that the second option is either equivalent or better than the first, but no worse, but I am confused why the first is even written that way in the first place. I do not understand why a temporary variable and a swap was needed.

Instead, couldn't we just have done something like:

T& T::operator=(T const& x) // x is a reference to the source
{ 
    this->member_var = x.member_var;
    //if we have to do a deep copy of something, implement that here
    return *this;
}

which doesn't use a copy constructor.

xskxzr
  • 10,946
  • 11
  • 32
  • 70
anonuser01
  • 1,307
  • 7
  • 14
  • I'm not sure what question you are asking exactly. Are you asking why the copy/swap idiom is used? – François Andrieux Feb 28 '20 at 01:47
  • @FrançoisAndrieux I'm asking why is a temporary object created in the first place instead of directly copying over `x` into `this`? – anonuser01 Feb 28 '20 at 01:48
  • *How* to copy `x` into `this` is what the assignment operator does, so that doesn't really clear anything up for me. Are you asking why we shouldn't use the last piece of code you provided (the one without the use of a copy constructor) instead of the copy/swap idiom? – François Andrieux Feb 28 '20 at 01:50
  • What if your object has dozens of members? The swap is soooo much more readable. The only downsize of swap is that it doesn't do a deep copy. – Jerry Jeremiah Feb 28 '20 at 01:50
  • 1
    @JerryJeremiah That depends entirely on how the copy constructor is implemented... Nothing about copy/swap forces shallow copying. – François Andrieux Feb 28 '20 at 01:52
  • @FrançoisAndrieux Yes, that's what I'm asking, although I'm also asking why a copy to a temporary object is needed (as shown in the first 2 snippets). It seems the logic behind it is to be able to reuse code, i.e., the copy constructor, but that seems to be done at the expense of creating a new temporary object for each assignment operator call. – anonuser01 Feb 28 '20 at 01:52
  • 1
    You're right in why we use copy/swap and the cost of it. Copy/swap isn't required and it isn't automatically the better solution. But it's pretty useful. One upside you may have missed from the linked question is exception safety. With copy/swap, either the assignment succeed or `this` is unchanged. `swap` should always be `noexcept` so only the copy should be able to fail. If it does, the `swap` never happens. Doing this pretty much always has a bit of overhead, and it isn't very different the overhead of using copy/swap. – François Andrieux Feb 28 '20 at 01:54
  • Note that the version without a reference (pass by value) takes advantage of move semantics to some extent. If you move assign to `this` the argument is move constructed, so there isn't really a copy made. There is an extra instance created, but move construction should be relatively cheap. – François Andrieux Feb 28 '20 at 01:56
  • The `exception safety` thing actually confused me at first glance, and I glossed over it. I'll go back to look at it in more detail. Regarding your last comment about move semantics: move semantics would only be used if `x` was an r-value right? And in this case, for `x` to be a r-value, the compiler would have to employ copy elision optimization because without it, my understanding is that the temporary `x` object would be created? – anonuser01 Feb 28 '20 at 02:01
  • 1
    When you call `operator=` if the object you pass is an rvalue than the argument `x` will be move constructed. For example in `foo = std::move(bar)` in `foo::operator=(foo x)` the argument `x` is move constructed from `bar`. Then `x` would be swapped with `*this`. But `x` us never an rvalue in this scenario only `std::move(bar)` produces an rvalue reference. – François Andrieux Feb 28 '20 at 02:14
  • Oh I see. What do you call `x` is this sentence? It's not an lvalue or an rvalue. – anonuser01 Feb 28 '20 at 02:29
  • 1
    `x` is an lvalue expression like any other named variable. It has a name (`x`) and you can take it's address (`&x`). How it was constructed doesn't change it's value category. – François Andrieux Feb 28 '20 at 02:51
  • @FrançoisAndrieux Hmm I see. It's not super intuitive to me that it's still considered an lvalue if a rvalue argument can be passed into it. – anonuser01 Feb 28 '20 at 03:25
  • 1
    The trick is that value categories apply to expressions, not the value they contain or represent. If you have `T x;` then `x` is an lvalue. If you have `T function();` then calling the function results is an rvalue. If you did `x = function();` then `function()` still returns an rvalue and `x` is still an lvalue even if they now represent the same state. – François Andrieux Feb 28 '20 at 03:35
  • 1
    @FrançoisAndrieux `function()` *is* an rvalue, it's not really correct to say "returns an rvalue" since rvalues are expressions, and functions do not return expressions – M.M Feb 28 '20 at 03:59
  • @M.M You're right. I meant to write "still results in an rvalue" in reference to "calling the function results is an rvalue". Edit : though it might have been better to omit word results entirely. – François Andrieux Feb 28 '20 at 04:06

1 Answers1

1

Your assignment operator is not exception-safe if there are multiple members:

T& T::operator=(T const& x) 
{ 
    this->member_var1 = x.member_var1; 
    this->member_var2 = x.member_var2; // if an exception occurs here, this->member_var1 will still be changed
    return *this;
}
xskxzr
  • 10,946
  • 11
  • 32
  • 70
  • 1
    That's not about thread-safety (without synchronization, multi-member swaps aren't magically "safe" either; just about any attempt to read data without synchronization or atomics can read garbage, particularly on systems with weakly ordered memory model, or due to tearing), it's about the strong exception guarantee. – ShadowRanger Feb 28 '20 at 05:11