Copy-swap idiom not recommended?
Asked Answered
B

0

1

For a long time time I though that the correct way to implement a copy assignment (for a non trivial class) was to use the copy-swap idiom.

struct A{
   ... pointer to data
   A(A const& other){
       ... complicated stuff, allocation, loops, etc
   }
   void swap(A& other){... cheap stuff ...}
   A& operator=(A const& other){
      A tmp{other};
      swap(other);
      return *this;
   }
};

But then I heard this talk https://www.youtube.com/watch?v=vLinb2fgkHk by Howard Hinnant where he says that the copy-swap idiom is good to fulfill the strong-exception guarrantee but it is an overkill in general.

He mentions that here: https://youtu.be/vLinb2fgkHk?t=2616

So he suggests implement the copy assignment explicitly and have a separate function strong_assing(A& target, A const& source) containing the copy-swap.

What I don't understand is how A& operator=(A const& other) should be implemented then?

Does he suggest to have something like this?

   A& operator=(A const& other){
        ... don't allocate if not necessary
        ... loop element by element
        ... clean the bare minimum if there is a throw 
   }

Is this what std::vector implementations do then? That is, they do not use the copy-swap idiom at the end? Doesn't the standard vector requires the strong-exception guarantee?

Byssus answered 22/7, 2018 at 8:35 Comment(9)
You should define "correct". The pitfall of copy and swap is that you have 3 objects peak instead of two objects peak while you don't necessary want to preserve an old value in case of assignment failure.Wingspread
std::vector can copy a buffer internally, then swap it. So it does not need copy and swap at the level of the assignment operator parameter.Rai
There is no "one right way" to implement a copy assignment operator. And the main point of the talk you reference is that copy/swap is not the "one right way" to implement a copy assignment operator, especially if you have vector and/or string data members (either directly or indirectly).Logistic
Here you can view for yourself an actual, industrial-grade vector copy assignment operator.Logistic
Thank you @HowardHinnant. I though that copy-swap was used because it was superior to A::operator=(const A& __x){if (this != &__x){...} ...}; (used in your linked code) but apparently it is still the correct way to start when only the basic-exception guarantee is necessary. no?Byssus
@alfC: The very best operator= start (and end) with = default. It is difficult for me to give a general recipe for this function. It should give the lhs the state of the rhs, keep the rhs state unchanged, usually create two distinct (unlinked) values, not leak any memory, have at least basic exception safety, and be as high performance as possible. A decent first cut at estimating performance is to count the number of allocations and deallocations.Logistic
@HowardHinnant If I understand correctly std::vector doesn't have the strong exception guarantee. And that is not bad thing since the strong exception guarantee is not required. I think I also understand that copy-swap (for a vector-like) will always call allocate for the third object, while obviously there are (runtime) cases in which allocations would not be necessary to copy a container.Byssus
@VTT, from what I learned here, apparently there are two "correct" ways, strong-exception guarantee (SEG) and basic-exception guarantee (BEG). The second can be faster in many cases. I wonder how to decide whether copy assignment should be SEG or BEG. I don't usually see this documented (e.g. en.cppreference.com/w/cpp/container/vector/operator%3D)Byssus
@alfC: If the spec says nothing about it, you can assume basic. When the spec says something different, it will look like the exception-clause for vector::push_back. The design philosophy that the committee used for deciding when to provide the strong guarantee was "when it does not add a performance penalty to do so."Logistic

© 2022 - 2024 — McMap. All rights reserved.