C++ move semantics from scratch (2022)

(cbarrete.com)

64 points | by todsacerdoti 5 days ago

8 comments

  • web3-is-a-scam 1 hour ago
    I loved writing C++ back in the day, C++98 was peak.

    I couldn’t fathom starting a new project with whatever the current C++ is now.

    • vardump 59 minutes ago
      C++98 forced the compiler to generate a lot of useless code. Newer semantics helps to remove this overhead.

      You can still write things the old way, if you like.

    • webdevver 32 minutes ago
      for me, its C++11. the absolute pinnacle of mankind.

      everything has been going downhill since then. coincidence? i think not!

      • medler 20 minutes ago
        The new changes in C++14, 17, and 20 are really nice. It feels like the language keeps getting cleaner and easier to use well
    • spacechild1 19 minutes ago
      Have you even tried modern C++? If no, how can you say that C++98 was peak?

      As someone who grew up with modern C++, I can't even imagine going back to C++98 because it feels so incredibly verbose. Just compare how you iterate over a std::map and print its items in C++98 vs C++23:

        // C++98:
        for (std::map<std::string, int>::const_iterator it = m.begin(); it != m.end(); ++it) {
            std::cout << it->first << ": " << it->second << "\n";
        }
      
        // C++23:
        for (const auto& [key, value] : m) {
            std::print("{}: {}\n", key, value);
        }
      
      Then there are all the features I would miss, for example:

        - auto
        - lambda functions and std::function
        - move semantics (I can return large objects from a function!)
        - std::unique_ptr and, to a lesser extent, std::shared_ptr
        - variadic templates
        - std::filesystem
        - std::chrono
        - std::thread, std::mutex, std::atomic, etc.
        - a well-defined memory model for multi-threaded programs
        - unordered containers
        - structured bindings
        - class template argument deducation
        - std::format
        - std::optional
        - std::variant
        - etc.
    • almostgotcaught 16 minutes ago
      hn has become literally just twitter level hottakes
  • criemen 2 hours ago
    Would it be fair to say that things are so complicated (compared to all other programming languages I've used in my professional life), because C++ pre-move semantics defaulted to deep copy semantics? It seems to be set apart in that choice from many other languages.
    • kccqzy 2 hours ago
      Deep copy is pedagogically and semantically the right choice for any mutable containers. You either make containers immutable or copies deep. Otherwise it's just an invitation for subtle bugs.
      • vlovich123 37 minutes ago
        Then explain that const isn’t deep and a const container can end up mutating state? Pretending like c++ has a consistent philosophy is amusing and pretending this happened because of pedagogy is amusing. It happened because in c assignment is a copy and c++ inherited this regardless of how dumb it is as a default for containers.
      • hgomersall 47 minutes ago
        No, it should move properly when passing by value (as in, essentially the rust move semantics). If you want a copy, that should be explicit.
      • criemen 2 hours ago
        I'm not sure about that - every time I copy an object I have to think through what happens, no matter the default semantics. C++ makes the deep copy case easier than other programming languages without top-level built-in support.
      • cjfd 47 minutes ago
        It certainly makes things easier. But it also makes some things very, very, very inefficient. I want a list with millions/billions of elements. I want to regularly change one of the elements somewhere in the middle. Good luck with the copying.
        • Maxatar 1 minute ago
          Why would you copy a whole list to modify a single element?
    • HarHarVeryFunny 58 minutes ago
      C++ supports both pass-by-value and pass-by-reference parameters. Pass-by-value means making a copy (a deep copy if it's a deep type), but you could always choose to optimize by passing large parameters by reference instead, and this is common practice.

      The real value of std::move is cases where you to HAVE to (effectively) make a deep copy, but still want to avoid the inefficiency. std::move supports this because moving means "stealing" the value from one variable and giving it to another. A common use case is move constructors for objects where you need to initialize the object's member variables, and can just move values passed by the caller rather than copying them.

      Another important use case for std::move is returning values from functions, where the compiler will automatically use move rather than copy if available, allowing you to define functions returning large/complex return types without having to worry about the efficiency.

      • w10-1 22 minutes ago
        1990's C practice was to document whether initialization values were copied or adopted. I'm curious why the concept became "move" rather than "adopt", since move gives the parameter/data agency instead of giving agency to the consuming component.
      • tubs 28 minutes ago
        (More so since c++17) std::move should not be used for returns because this pessimises optimisations.
    • htfy96 2 hours ago
      Correct. As someone who maintain a 16-year-old C++ code base with new features added every day, The status quo is the best incremental improvement over deep copy semantics.

      There are better choices if everything is built from scratch, but changing wheels from a running car isn't easy.

    • anonnon 48 minutes ago
      > defaulted to deep copy semantics

      It defaulted to pass-by-value, with shallow copy semantics, as opposed to pass by reference.

      • HarHarVeryFunny 8 minutes ago
        No, pass-by-value means copying, which means whatever the copy constructor of the type implements, which for all standard types means a deep copy.

        You COULD define you own type where the copy constructor did something other than deep copy (i.e. something other than copy!), just as you could choose to take a hand gun and shoot yourself in the foot.

  • DLoupe 29 minutes ago
    "Another difference in Rust is that values cannot be used after a move, while they simply "should not be used, mostly" in C++"

    That's one of my biggest issues with C++ today. Objects that can be moved must support a "my value was moved out" state. So every access to the object usually starts with "if (have-a-value())". It also means that the destructor is called for an object that won't be used anymore.

  • macleginn 59 minutes ago
    Irregardless of the main topic of the post, combining a struct definition with a constructor is additionally confusing.
  • TimorousBestie 3 hours ago
    The article is a really good exposition of move semantics, but unfortunately many modern C++ features benefit from the pedagogical technique of “imagine this feature didn’t exist, this is why someone would want to develop it.”

    I say unfortunately because this doesn’t scale. A junior programmer doesn’t have the time to process 30 years of C++’s historical development.

    Mathematics (which has a much longer history and the same pedagogical problem) gets around this by consolidating foundations (Bourbaki-style enriched set theory -> category theory -> homotopy type theory, perhaps?) and by compartmentalization (a commutative algebraist usually doesn’t care about PDEs and vice versa).

    I don’t see C++ taking either route, realistically.

    • cyanmagenta 2 hours ago
      If we want to make the math analogy, C++ seems more like the language of math (basic algebra, the notion of proofs, etc.) that everyone uses, and the compartmentalization comes when you start to apply it to specific fields (number theory, etc.). That same concept exists in the C++ community: the people who care about stuff like asynchronous networking libraries aren’t usually the people who care about SIMD math libraries, and vice versa.

      I also wonder if most junior C++ programmers can shortcut a bit by just using common patterns. Articles like these I’ve always thought were geared more toward experienced programmers who are intellectually curious about the inner workings of the language.

    • pesfandiar 1 hour ago
      Mathematics doesn't need to remain backward compatible.
      • somethingsome 1 hour ago
        IMO math is backward compatible by default unless you change the foundational axioms (rare occurrence).

        In particular you can most of the time define morphisms between concepts.

    • einpoklum 1 hour ago
      > I don’t see C++ taking either route, realistically.

      But it has been taking the "compartmentalization" route: Once a new, nicer/safer/terser idiom to express something in code emerges, people being taught the language are directed to use that, without looking into the "compartment". Some of this compartmentalization is in the language itself, some in the standard library, and some is more in the 'wild' and programming customs.

      It's true, though, that if you want to write your own library, flexibly enough for public use - or otherwise cater to whatever any other programmer might throw at you - you do have to dive in rather deep into the arcane specifics.

  • anonnon 49 minutes ago
    > int& lvalueRef = (int&)x;

    > int&& rvalueRef = (int&&)x;

    Why are they casting x here?

  • revivalizer 3 hours ago
    This is a really well written article that explains the concepts straightforwardly. I had never bothered to understand this before.

    ... because I gave up on C++ in 2011, after reading Scott Meyers excellent Effective C++. It made me realize I had no desire to use a language that made it so difficult to use it correctly.

    • wvenable 1 hour ago
      I like working in C++ (I don't do it professionally though) and I just never bother to read up on all the weird semantic stuff. I think the more you look into C++ the more irrational it seems but I generally just program in it like it's any other language and it's fine. It's actually even somewhat enjoyable.
    • ckcheng 2 hours ago
      I retired from wanting to write C++ when Scott Meyers retired from writing more Effective Modern C++.
      • webdevver 31 minutes ago
        scott could not have picked a better time to retire tbh. dude really sold the top.
    • chuckadams 2 hours ago
      I had exactly the same reaction to Effective C++, and I'd learned it back in the 90's (my first compiler didn't even support templates!). It's a wonderful book for sure, but it's a wonderfully detailed map to a minefield. The existence of guidelines like the "Rule of 7" should be raising questions as to why such a rule needs to exist in the first place.

      As for this article, it really did de-mystify those strange foo&& things for me. I had no idea that they're functionally identical to references and that what C++ does with them is left up to convention. But I still felt like I had to fight against sanity loss from a horrid realization.

      • scotty79 2 hours ago
        I don't get what's bad about rule 7. And I haven't really programmed in C++ for a decade. When you are calling derived object through a base class pointer you have a choice if you want to call the function of the base class or the function of the derived class. If you don't make it virtual it's called by pointer type, if you do, it's called by pointee type. Same goes for the destructors with only difference being that in case of virtual destructor the deatructor of a base class will be called automatically after the destructor of the derived class. So basically if you want to override methods or the destructor make your functions virtual, including the destructor.

        Does it lead to problems? Surely. Should all metods be virtual by default? Probably. Should there be some keyword that indicates in derived class that a method intentionally shadows a non virtual method from the base class? Yes.

        It's not a great human oriented design but it's consistent.

        • chuckadams 2 hours ago
          Apologies, I was referring to a "Rule of 7", but I more or less hallucinated it, since I'd heard the old "rule of 3" then "rule of 5" had been revised again, and thought they were maybe going with prime numbers?

          https://en.cppreference.com/w/cpp/language/rule_of_three.htm...

          The confusion kind of speaks for itself. The language is a construction set where the primary building block is razor blades.

          • quuxplusone 1 hour ago
            Ten years ago I gave a C++ conference talk titled "The Rule of Seven (Plus or Minus Two)" — a reference to the "magic number seven" meme

            https://en.wikipedia.org/wiki/The_Magical_Number_Seven,_Plus...

            and alluding to the fact that besides the well-established "Rule of 5" (copy construction and assignment, move construction and assignment, and destruction) a C++ class author also needs to at least think about whether to provide: default constructor, ADL swap, equality comparison operator, and/or specialized std::hash. And maybe a few more I'm forgetting right now.

            In hindsight (without rewatching it right now) I remember my thesis in that talk as erring too much on the side of "isn't this confusing? how exciting! be worried about the state of things!" These days I'd try harder to convey "it's not really that hard; yes there are a lot of customization knobs, but boring rules of thumb generally suffice; newbies shouldn't actually lose sleep over this stuff; just remember these simple guidelines."

          • einpoklum 1 hour ago
            It's important to notice that you can keep well out of trouble by sticking to the rule of _zero_, i.e. relying on the defaults for copy&move ctors, assignment operators and destructor.

            So, the best advice is: Don't mess with the razor blades. And these days, C++ gives you enough in the standard library to avoid messing with them yourself. But since this is C++, you _can_ always open up the safety casing and messing with things, if you really want to.

  • mlmonkey 2 hours ago
    Whenever I'm dealing with C++, I get tripped by the most basic of things: like for example, why use "&&" for what appears to be a pointer to a pointer? And if this indeed the case, why is int&& x compatible with int& y ?? Make up your mind: is it a pointer to a pointer, or a pointer to an int?!?

    I have steadfastly avoided dealing with C++ for almost 30 years, and I am grateful that I did not have to. It seems like such a messy language with overloaded operators and symbols ( don't even get me started on Lambdas!)

    • TinkersW 2 hours ago
      If you had read like even the basic part of that article you would know that && is not a pointer to a pointer.

      Anyway C++ isn't as complicated as people say, most of the so called complexity exists for a reason, so if you understand the reasoning it tends to make logical sense.

      You can also mostly just stick to the core subset of the language, and only use the more obscure stuff when it is actually needed(which isn't that often, but I'm glad it exists when I need it). And move semantics is not hard to understand IMO.

      • pizza234 1 hour ago
        > Anyway C++ isn't as complicated as people say, most of the so called complexity exists for a reason, so if you understand the reasoning it tends to make logical sense.

        I think there was a comment on HN by Walter Bright, saying that at some point, C++ became too complex to be fully understood by a single person.

        > You can also mostly just stick to the core subset of the language

        This works well for tightly controlled codebases (e.g. Quake by Carmack), but I'm not sure how this work in general, especially when project owners change over time.

      • mlmonkey 2 hours ago
        > If you had read like even the basic part of that article you would know that && is not a pointer to a pointer.

        OK, let me ask this: what is "&&" ? Is it a boolean AND ? Where in that article is it explained what "&&" is, other than just handwaving, saying "it's an rvalue".

        For someone who's used to seeing "&" as an "address of" operator (or, a pointer), why wouldn't "&&" mean "address of pointer" ?

        • throwaway150 1 hour ago
          Your comments are very confusing.

          > For someone who's used to seeing "&" as an "address of" operator (or, a pointer)

          You must be talking about "&something" which takes the "address of something" but the OP does not talk about this at all. You know this because you wrote in your other comment ...

          > And if this indeed the case, why is int&& x compatible with int& y ?

          So you clearly understand the OP is discussing "int&&" and "int&". Those are totally different from "&something". Even a cursory reading of the OP should tell you these are references, not the "address of something" that you're probably more familiar with.

          One is rvalue reference and the other is lvalue reference and I agree that the article could have explained it better what they mean. But the OP doesn't seem to be an introductory piece. It's clearly aimed at intermediate to advanced C++ developers. What I find confusing is that you're mixing up something specific like "int&&" with "&something", which are entirely different concepts.

          I mean when have you ever seen "int&" to be "address of" or "pointer"? You have only seen "&something" and "int*" and "int**" be "address of" or "pointer", haven't you?

      • djmips 1 hour ago
        Unless, you work with a large team of astronauts who ignore the coding guidelines that say to stick with a core subset but leadership doesn't reign them in and eventually you end up with a grotesque tower of babel with untold horrors that even experienced coders will be sickened by.
    • wocram 2 hours ago
      && is not a pointer to a pointer, it's a temporary value. There is a huge amount of cognitive overhead in normal cpp usage because over time we have found that many of the default behaviors are wrong.
    • einpoklum 2 hours ago
      > "Whenever I'm dealing with C++" ... "I have steadfastly avoided dealing with C++"

      So, basically, you're just trolling us about a language you avoid using. Thanks, that's very helpful.