[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)