12 ms·
> So apparently, move does not prevent generation of a copy, but the empty string instead of expected text “Dave” is very interesting. Apparently, after termina
by bluescarni 2y ago
> So apparently, move does not prevent generation of a copy, but the empty string instead of expected text “Dave” is very interesting. Apparently, after termination of show after the move, the object is invalidated. This does not affect the Person object, but only the string object. Recognize that I speak about a factual behavior on the hardware. I think we have undefined behavior here. And no compilation error.
There is a lot of wrong in this paragraph:
- a "copy" was not generated, at least not in the sense that the actual content of the string was copied anywhere;
- there's no undefined behaviour here and no invalidation of the string. Standard library types are required to be left in an unspecified but valid state after move. "Valid" here means that you can go on and inspect the state of the string after move, so you can query whether it is empty or not, count the number of characters, etc. etc. "Unspecified" means that the implementation gets to decide what is the status of the string after move. For long enough strings, typical implementation strategy is to set the moved-from string in an empty state.
- flohofwoe 2y ago> at least not in the sense that the actual content of the string was copied anywhere ...unless it's a short string within the limits of the small-string-optimization capacity. I think what confuses many people is that a C++ move assignment still can copy a significant amount of bytes since it's just a flat copy plus 'giving up' ownership of dangling data in the source object. For a POD struct, 'move assignment' and 'copy assignment' are identical in terms of cost.
- mort96 2y agoI mean it'll copy 3 pointers worth of data in all cases. It's just that for short strings, those 3 pointers worth of data contains the text of the string.
- fluoridation 2y agoI feel like that's a pedantic detail. True, yes, but irrelevant. You may as well also point out that the return address is going to be copied to the instruction pointer when the constructor returns.
- jvanderbot 2y agoIt should be, but it's very much not in the real world at least as far as I've seen. Using std::move for anything other than "unique ownership without pointers" really messes things up. People put std::move everywhere expecting performance gains, just like we used to put "&" everywhere expecting performance gains. It's a bit of cargo cultism that can be nicely dispelled by realizing std::move is just std::copy with a compiler-defined constructor invocation potentially run to determine the old value. With that phrasing, it's hard to hallucinate performance gains that might come automatically.
- colejohnson66 2y agoIn fact, using std::move everywhere can actually make your performance worse! https://devblogs.microsoft.com/oldnewthing/20231124-00/?p=109059 https://devblogs.microsoft.com/oldnewthing/20231124-00/?p=10...
- gpderetta 2y ago> std::move is just std::copy with a compiler-defined constructor invocation potentially run to determine the old value I have no idea what that means. std::move is a cast to an rvalue reference. That can potentially trigger a specific overloaded function to be selected and possibly, ultimately, a move constructor or assignment operator to be called. For an explicit move to be profitable, an expression would have otherwise chosen a copy constructor for a type with an expensive copy constructor and a cheap move constructor. std::copy is a range algorithm, not sure what's the relevance.
- jvanderbot 2y agoYes, typed too fast. I meant the explicit copy constructor. Luckly, HN will hide my garbage text quickly enough. Thanks for the correction!
- Asraelite 2y agoI think it's a worthwhile distinction to bring up because it highlights a common misconception people have about strings and vectors. A string value is not the string content itself, just a small struct containing a pointer and other metadata. If we're talking about the in-depth semantics of a language then it's important to point out that this struct is the string, and the array of UTF-8 characters it points to is not. C++ obfuscates this distinction because of how it automatically deep copies vectors and strings for you in many cases.
- gpderetta 2y agoYou can think of a c++ move as a shallow copy that takes ownership of all objects originally owned by the source.
- jvanderbot 2y agoThe real gem of the article is the interlude. E.g., reaching back to C days and pointing out that "It's either copy, or pointer". Once someone has that mental model solidly in hand, all the syntax sugar in the world cannot harm you. Also "It was an ergonomic advancement." hides a lot of the overwrought syntax sugar in C++ that causes it to be such a weird language if you come from elsewhere. But still an excellent insight into the state of affairs. I think the "Apparently" language makes it seem like this is some kind of accident that nobody would know about, when really the author was probably just being a creative writer, and the example was fundamental to the post.
- nemetroid 2y agoThe same is true of Rust. I have no idea why the author decided to print addresses only for C++ and not for Rust. // (1) struct Person { name: String, age: u8, } fn show(person: Person) { println!("Person record is at address {:p}", &person); println!("{} is {} years old", person.name, person.age); } fn main() { let p = Person { name: "Dave".to_string(), age: 42 }; // (2) println!("Person record is at address {:p}", &p); show(p); // (3) } Its output is: Person record is at address 0x7ffcfb2b4e40 Person record is at address 0x7ffcfb2b4ec0 Dave is 42 years old
- bluGill 2y agothere is a lot wrong but your analisys misses the elephant: the function takes a copy and so a copy must be generated. std::move will move if possible but in this case move isn't possible and so a copy will be made. Move is allowed to not move because in generic code you don't want to have to check for if move is possible for the type in question.
- littlestymaar 2y agoC++ making the most inscrutable semantic possible, speedrun any %.
- GrantMoyer 2y agoIn the case of the example, there is a move, and std::move works in the example. The function, show, doesn't take a copy, it takes a Person object. Persons can be copy constructed or move constructed (both constructors are implicit, since there's no user-defined constructors). std::move returns an r-value reference to main's p, so Person's implicit move constructor is called, and show's p argument is move constructed from main's p. The reported address changes because moving creates a new object in C++, but the moved-to object may take ownership of the heap allocated memory and other resources from the moved-from object. In this case, the moved-to Person takes ownership of the heap allocation from the moved-from Person's string member and sets the moved-from Person's string member to an empty string. Without std::move, show's p is copy constructed, including its string member.
- virtualritz 2y ago> "Unspecified" means that the implementation gets to decide what is the status of the string after move. For long enough strings, typical implementation strategy is to set the moved-from string in an empty state. Thusly, what happens in code that accesses the string after the move is UB. In the implementation of C++ the article uses the string was just empty. But for all we know it may still contain a 1:1 copy of the original or 20 copies or a gobbledygook of bytes. Any code that relies on the string being something (even empty) may behave different if it isn't. That's the very definition of UB. "A typical implementation strategy" is meaningless for someone writing code against a language specification. You're then writing code against a specific compiler/std lib and that's fine. But let's be honest about it.
- UncleMeat 2y agoThat's not what UB means. "This will behave differently on different implementations" is implementation defined behavior. Compilers are not allowed to assume that implementation defined behavior never occurs or reject your program if they can prove that it happens. Undefined behavior is a stronger statement and says that if the behavior occurs then the entire program is simply not valid. This allows the compiler to make vastly more aggressive changes to your program.
- Maxatar 2y agoThere is nothing in the standard or definition of C++ that states that undefined behavior renders a program invalid. On the contrary the actual C++ standard explicitly states that permissible undefined behavior includes, and I quote "behaving during translation or program execution in a documented manner characteristic of the environment". It's also worth noting that numerous well known and used C++ libraries explicitly make use of undefined behavior, including boost, Folly, Qt. Furthermore, as weird and ironic as this sounds, implementing cryptographic libraries is not possible without undefined behavior.
- gpderetta 2y ago"valid program" is not really a term that is used in the standard (I only count one normative usage). What the standard does say is: "A conforming implementation executing a well-formed program shall produce the same observable behavior as one of the possible executions of the corresponding instance of the abstract machine with the same program and the same input. However, if any such execution contains an undefined operation, this document places no requirement on the implementation executing that program with that input (not even with regard to operations preceding the first undefined operation)." I.e. a program the contains UB is undefined. Of course, as you observer, an implementation can go beyond the standard and extend the abstract machine to give defined semantics to those undefined operations. That's still different from implementation defined behaviour, where a conforming implementation must give defined semantics.