[HN Gopher] The copy and swap idiom in C++
       ___________________________________________________________________
        
       The copy and swap idiom in C++
        
       Author : todsacerdoti
       Score  : 35 points
       Date   : 2022-08-11 06:03 UTC (1 days ago)
        
 (HTM) web link (www.sandordargo.com)
 (TXT) w3m dump (www.sandordargo.com)
        
       | nly wrote:
       | This idiom is largely retired in modern C++.
       | 
       | It's generally better to simply not implement operator= and take
       | the compiler generated default implementation for both copy and
       | move assignment. Then design your class to have semantics such
       | that member-wise assignment does the Right Thing (tm). C style.
       | If you want an idiom for this it's generally called the 'Rule of
       | Zero'
       | 
       | You get swap() for free since std::swap will do the optimal
       | thing.
        
         | beached_whale wrote:
         | Most of the time. In container like code, copy/swap gives easy
         | strong exception guarantee, but that is rarely needed
        
           | nly wrote:
           | Achieving exception safety while copying containers,
           | specifically, is generally simple because their states tend
           | to be very easy to reason about. For example, making sure you
           | destroy elements 0 thru [n-1] in a vector if inserting
           | element [n] in to it fails.
           | 
           | Move, and thus swap (since it's 3 moves), should essentially
           | always be noexcept (never throw)
        
         | synergy20 wrote:
         | indeed 'rule of 0' made many idioms go away. just use the
         | standard containers along with smart-pointers for 99% use cases
         | and your c++ life suddenly becomes 99% easier, those come with
         | RAII for free.
        
       | dataflow wrote:
       | Copy-and-swap/move is fun and all, but what they don't point out
       | to you about it is that it prevents the base class operator= from
       | being called when there's inheritance (because it's bypassing
       | operator= entirely).
       | 
       | i.e. it might be fine at the app-level since you might know all
       | your callers in that case, but you shouldn't do it in a library
       | that others might want to use, or your code will behave
       | erratically.
        
       | stefanos82 wrote:
       | I have managed to convince myself to think C++ is actually a DIY
       | Tool Center that offers me an abundant set of tools I can choose
       | from, upon need (project-wise).
       | 
       | This way I know I don't need to learn the whole language _at this
       | particular time_ ; only the necessary parts I can use to produce
       | the desired output, which really helps me control my stress.
        
       | jbandela1 wrote:
       | Post C++11, it is probably better to use the "copy and move"
       | idiom.                   MyClass& operator=(const MyClass& other)
       | {                return (*this) = MyClass(other);         }
        
         | vlovich123 wrote:
         | In any version of the language it's better to decompose it so
         | that you never implement any of these. You either have:
         | 
         | 1. A class that's responsible for managing _exactly_ one
         | instance of a resource (e.g. FileDescriptor). This class
         | defines whether or not a resource is copyable and /or movable.
         | 
         | 2. A dynamically sized container that needs to offer certain
         | kinds of guarantees.
         | 
         | 3. A composite class that may contain zero or more instances of
         | 1, 2, and/or 3.
         | 
         | The vast majority of classes you write are always going to be
         | 3. Option 3 should NEVER specify move/copy
         | constructors/assignment operators and instead should inherit
         | that from whatever is being stored.
         | 
         | Don't do #2 unless you _really_ know what you 're doing. Prefer
         | to use existing well-behaved containers from STL. If you need
         | 3p ones, Folly and Boost are probably good ones. I don't know
         | how ABSEIL fares around exception safety when exceptions are
         | enabled so YMMV there if you're doing things in an exception
         | context. It can be hard to properly make sure that you inherit
         | the copyability / movability from the underlying type (& also
         | noexcept inheritance).
         | 
         | For #1, KISS is a _very_ good principle. Just make sure you
         | always leave resources in a consistent state. Move helps here a
         | lot but can be emulated with copy + swap pre-C++11. Here 's an
         | example of what a copyable and movable resource might look
         | like:                   // Terrible idea - don't actually do
         | this in practice.         class AutoDupOnCopyFd {
         | public:           AutoDupOnCopyFd(const AutoDupOnCopyFd& copy):
         | _fd(dup(copy._fd)) {             if (_fd == -1) { throw ... }
         | }           AutoDupOnCopyFd(AutoDupOnCopyFd&& owned) noexcept:
         | _fd(std::exchange(copy._fd, -1)) {}
         | ~AutoDupOnCopyFd() noexcept { close(_fd); }
         | AutoDupOnCopyFd& operator=(const AutoDupOnCopyFd& other) {
         | // No need to check for assignment to self but you can if you
         | think you're likely to have this happen             // to avoid
         | the syscall (copy to self is typically rare)
         | AutoDupOnCopyFd copy(other);             *this = *copy;
         | return *this;           }                AutoDupOnCopyFd&
         | operator=(AutoDupOnCopyFd&& other) noexcept {             using
         | std::swap;             swap(_fd, other._fd);             return
         | *this;           }         private:           int _fd;
         | };
         | 
         | Now `AutoDupOnCopyFd` can be nested in any composite class &
         | the composite class will automatically inherit the right set of
         | copy / move semantics with correct exception safety. The rule
         | of 0 really is a powerful concept. This breaks down if you
         | start trying to make 1 class responsible for multiple
         | resources. Don't do that. Use resource classes +
         | containers/composite classes to do that.
         | 
         | You can also (ab)use unique_ptr for unique ownership although I
         | still prefer explicitly named classes (no confusion with `->`
         | vs `.`, easier to understand for the vast majority of coders).
         | 
         | It's a powerful concept to internalize to level up your C++
         | game but it's only applicable to C++'s ownership model. Rust
         | went a more teachable path that doesn't have these foot guns.
        
       | [deleted]
        
       | AlexanderDhoore wrote:
       | I used to teach C++ courses as a consultant. This kind of thing
       | was part of the gospel I was trying to spread. Educate my follow
       | software developers on the merits of good C++ memory management.
       | It all felt so powerful and cool.
       | 
       | But after some time you realise that nobody is smart enough to
       | keep all of these idioms and arbitrary C++ rules in their head.
       | The only reason I could do it was because I put so much time into
       | preparing my courses. So all of your collegues will keep writing
       | terrible C++ code. And you will be left frustrated that nobody is
       | "doing C++ right".
       | 
       | I guess one could chill down and accept the chaos. I chose to
       | leave C++ behind.
        
         | nocman wrote:
         | My opinion is similar to others in this thread. The complexity
         | you are required to manage in order to push the limits of C++
         | makes doing so not worth it. There are far too many ways it can
         | bite you, and the benefits you gain by moving beyond a small
         | subset of C++'s capabilities were not worth the hassle for me.
         | 
         | I _really_ wanted them to be worth it, and spent years trying
         | to make it worth it. I guess in the long run it was worth the
         | time spent, because it caused me to explore other possibilities
         | that I might not otherwise have considered (in particular, Lisp
         | and Scheme-like languages, which I doubt I would have otherwise
         | given a second look -- they were just  "weird languages that we
         | spent a couple of weeks looking at in college - and oh, yeah
         | what's up with all those freaking parentheses?").
        
           | josefx wrote:
           | > The complexity you are required to manage in order to push
           | the limits of C++ makes doing so not worth it.
           | 
           | Sometimes the point is just having it possible. Sometimes I
           | need to get that last bit of performance out of some code.
           | 
           | > (in particular, Lisp and Scheme-like languages
           | 
           | If your performance requirements are more in the scripted
           | range then C++ will seem like overkill. I often use python
           | for quick and dirty tools, however every other time I end up
           | rewriting things in C++ because something that should finish
           | almost instantly takes half an hour.
        
         | jack_h wrote:
         | I've been using C++ for around 20 years now. I've spent a lot
         | of time learning the language, although I don't know any of
         | C++20 yet.
         | 
         | The issue with C++ is that the 'proper' way to do something
         | almost always requires an encyclopedic knowledge of the
         | language; any other solution you come up with will likely be
         | wrong or at the very least suboptimal for esoteric reasons.
         | It's not that people aren't smart enough, it's that there's not
         | enough time to really learn the language. I know I've
         | sacrificed sleep and social life just doing deep dives into the
         | language to really understand it. The reason I don't know C++20
         | yet is because I'm not willing to make those personal
         | sacrifices anymore, and even if we could use C++20 where I work
         | we don't have infinite time to learn it before being
         | productive.
        
           | gabereiser wrote:
           | This. That encyclopedic knowledge of gotcha's and work
           | arounds and such is why people hate C++ so much. It's far
           | beyond tolerable. If you were to restrict everything to just
           | c++20 (force shard_ptr, etc) it would still be something only
           | battle hardened adventurers would pursue. Languages like Go
           | and Nim and the all mighty Rust try to take away complexity
           | for a bit of convention and provide safety for us mortals
           | that just want to get an idea across.
           | 
           | For those that are weathering the storm in C++, I commend
           | you, but you're going to need therapy. (joke but the effects
           | of stress are real).
        
             | synergy20 wrote:
             | I somehow feel Rust is even more complex than C++
             | 
             | For C++ I settle down with c++17|c++20 and they're still
             | complex, but not that bad though.
             | 
             | Go might be simpler but C++ can do whatever Go does and I
             | tried to master both, it did not work, so I had to pick one
             | and that one has to be c++ for my use cases.
        
               | gabereiser wrote:
               | Getting around Rust's syntax is pretty much the whole
               | battle.
        
               | 3836293648 wrote:
               | And the borrow checker
        
               | ChadNauseam wrote:
               | Is Rust syntax really that different? I've only written a
               | little C++, but it seems pretty similar to me. They
               | mostly just switched from `type x = ...` to `let x: type
               | = ...` and let you return stuff by putting an expression
               | at the end of a block, right?
        
               | gabereiser wrote:
               | Wait until you need a heap allocated resource counted
               | mutex. Or have a struct that out lives it's creator.
               | 
               | &mut Arc<Mutex<Option<Box<MyStruct>>>>
        
               | tialaramex wrote:
               | Right, but while that's a bunch of stuff the _syntax_
               | here isn 't intimidating.
               | 
               | It's a mutable reference to an Arc of a Mutex of an
               | Option of a Box of MyStruct.
               | 
               | You presumably could build yourself one of these in C++
               | although since the standard mutex in C++ can't actually
               | protect anything (you're supposed to just remember to
               | lock it anywhere you need to, you know, because C++ is a
               | language for people who never make mistakes) you would
               | need to build that part yourself.
               | 
               | And if you _did_ build that yourself in C++ the syntax
               | for the type would look rather similar, although I guess
               | C++ allows you to just say auto meaning,  "I dunno,
               | guess" for the return type in a function signature
               | whereas Rust insists you must actually write return types
               | and don't leave them for the compiler to figure out.
        
             | kllrnohj wrote:
             | > For those that are weathering the storm in C++, I commend
             | you, but you're going to need therapy. (joke but the
             | effects of stress are real).
             | 
             | Every new release of C++ tends to reduce, not increase, my
             | stress. Many of these patterns, including this one, just
             | largely stop being things entirely. Rule of zero and all
             | that.
        
             | jandrewrogers wrote:
             | C++17 and especially C++20 are reasonable programming
             | languages that you rarely have to fight or work around.
             | Things have become much simpler. Anything prior to C++11
             | was sufficiently awful to work in that I used other
             | languages. They really resurrected the language with C++11.
             | 
             | It can be messy, because backward compatibility, but a lot
             | of newer code bases start time with idiomatic C++17. The
             | standard library is the primary place you'll still run into
             | old style C++.
        
         | yazzku wrote:
         | I'm on a similar boat as others here. I used to read all of
         | Bjarne's books back in the day. Now I barely keep up with the
         | language, and only update my knowledge of it enough to keep up
         | with work -- and companies usually lag several standards
         | behind. For my side projects, I've switched to C, which has a
         | much smaller cognitive overload and allows to me focus more on
         | the actual problem, or garbage-collected languages when I just
         | need something quick.
        
           | synergy20 wrote:
           | I like C a _lot_, just wish it has more well-established data
           | structure and algorithm collection like c++'s STL library to
           | speed up coding, also wish it had a better way to do RAII-
           | like resource management. Someone please invent c+ that can
           | reuse all existing c code and even some c++ code, but more
           | capable than c, much simpler than c++.
        
         | rambojazz wrote:
         | I loved C++, for a while. I chose to leave its grammar's
         | complexity behind too.
        
         | beached_whale wrote:
         | Less is more is what most code should strive for these days.
         | The copy/move ctors and assignment ops should be rare and are
         | often a single resp violation otherwise. Code like optional or
         | smart pointers sure, they are fair game for that, but business
         | logic generally shouldnt bother.
        
         | pjmlp wrote:
         | C++ is one of my favourite languages, yet I second the feeling,
         | sometimes preaching good practices feels like fighting
         | windmills, so I spend most of my time on languages where I
         | don't need to discuss about the virtues of bounds checking or
         | exceptions every single day.
        
         | billti wrote:
         | When I was "seriously" using C++ I created myself a bunch of
         | flashcards (using the Quizlet app) to regularly test myself
         | (spaced repetition) on various C++ idioms and rules; such as
         | when to implement which copy/assignment/move constructors and
         | the correct patterns, how to use std::forward correctly, the
         | footguns in various types of casting or initialization, etc.
         | 
         | I recently had cause to start using C++ again and went over the
         | cards after about a year break. It was depressing how few I
         | could answer correctly.
         | 
         | C++ is great if you're great with it, but that takes a __lot__
         | of effort, and it can be a minefield otherwise.
        
           | 0xfaded wrote:
           | I have a notebook I filled with Q&As for every point in The
           | CPP Programming Language that surprised me. It's about 40
           | pages I occasionally read through, though I've been meaning
           | to convert it to spaced repetition.
        
         | kllrnohj wrote:
         | Nothing about this problem seems unique to C++ or even made
         | more difficult by C++. In most languages it's a challenge to
         | ensure objects stay in a valid state if an exception is thrown
         | in the middle of a series of mutations. But that's also the
         | type of problem that you very rarely need to tackle. It's not
         | like you're making a transactional system with rollbacks every
         | time you want to go poke a few swing gui elements, after all.
        
       ___________________________________________________________________
       (page generated 2022-08-12 23:01 UTC)