2

I'm bit confused why the std::move(std::string) not making the passed std::string argument to an empty state (I mean std::string size as 0 and its internal buffer to point to nullptr after call to std::move(std::string)). This is a sample code

#include <iostream>
#include <string>

void print(std::string& str) {
    std::cout << "lref\n";
    std::cout << str << "\n" << std::endl;
}

void print(const std::string& str) {
    std::cout << "const lref\n";
    std::cout << str << "\n" << std::endl;
}

void print(std::string&& str) {
    std::cout << "rref\n";
    std::cout << str << "\n" << std::endl;
}

int main() {
    std::string str_a = "Hello, ";
    std::string str_b = "world!";
    
    print(str_a);
    print(str_b);
    print(str_a + str_b);
    
    print(std::move(str_a));
    print(str_a); // was expecting str_a to be empty but still prints Hello, 
    
    return 0;
}
Harry
  • 2,177
  • 1
  • 19
  • 33
  • 3
    It's not supposed to do that. The moved object is supposed to be in a valid but indeterminate state. – drescherjm Dec 23 '20 at 15:16
  • 4
    You expected wrong. `std::move` only create an rvalue reference, it doesn't "move" anything - this has to be done through move constructor or move assignment. –  Dec 23 '20 at 15:17
  • It doesn't have to do that. All it has to do is leave the moved-from object in a state that is unspecified but valid to reassign to. Don't use a moved-from object until you reassign or otherwise 'reset' it, because you can't depend on its state. – underscore_d Dec 23 '20 at 15:17
  • 2
    Does this answer your question? [Why does moving std::optional not reset state](https://stackoverflow.com/questions/51805059/why-does-moving-stdoptional-not-reset-state) – underscore_d Dec 23 '20 at 15:18
  • 1
    For instance, your implementation might do short-string optimisation (SSO) and thus not even point to a buffer, just store the `char`s internally. In that case, there's nothing to free, and no danger of double-freeing the same (nonexistent) buffer, so it needn't clear anything. But again, whether it does or does not do that doesn't matter in practice, because you just shouldn't use the object after moving, unless you have first reassigned/reset it to a known state, something that such move operations don't give. – underscore_d Dec 23 '20 at 15:19
  • @underscore_d if I reassign `str_a` will the other object (to which I moved) reflect the new reassigned value? – Harry Dec 23 '20 at 15:32
  • @Harry Of course not. Like any other C/C++ object, unless designed otherwise, `std::string` is a value type, not a reference type. Thus, assigning to `str_a` only changes anything as seen by people who look at `str_a`, or a reference to it, not anything else. – underscore_d Dec 23 '20 at 15:35
  • @underscore_d lets say I've a global variable `g_str` and `str_a = "Hello, "` inside `main` and function`foo(std::string&& a) { g_str = std::move(a); }`. Now in `main` when I call `foo(std::move(str_a); str_a = "abc";`. Does `g_str` print `Hello, ` or `abc` after reassigning `str_a`? – Harry Dec 23 '20 at 15:42
  • @Harry The former, as I already explained, and as is easy for you to test, and reason about with the knowledge that `std::string` is not a reference type. – underscore_d Dec 23 '20 at 15:43
  • 1
    Even if the string was *actually* moved from (because it was *not* in this code), the string is left in a valid, but unspecified state. I find that to be, generally, a good expectation: if you `std::move` an object, the object is suitable for re-assignment or destruction, and that's about it... with some extra guarantees for some kinds of objects (e.g., std::vector and std::unique_ptr and std::shared_ptr have stronger guarantees). Howard Hinnant did not like my "hands-off" take, he'd say (paraphrased) the object moved from is whatever state the programmer guarantees. – Eljay Dec 23 '20 at 15:45
  • @underscore_d The reason I asked is because I am thinking `g_str` internal `char* buffer` will now point to `str_a` internal `char* buffer` after `foo(std::move(a)` call, thus preventing allocating new memory and copying of data – Harry Dec 23 '20 at 15:51
  • @Harry If that happened to an optimisation that someone did, they would also know to stop referring to the same buffer from the moved-from string, precisely to avoid having two references to it and the possibility of interference or double-frees. Trust stdlib implementors. – underscore_d Dec 23 '20 at 15:57
  • @underscore_d so, both `str_b = std::move(str_a)` and `str_b = str_a` are gonna mean the same other than invalid state in case of `std::move`, basically there is no performance benefit I'm gonna get from `str_b = std::move(str_a)`, `str_b` is gonna have its own new memory allocated and copying of data from `str_a` to `str_b` still happens. – Harry Dec 23 '20 at 16:44
  • @Harry No, that is not true. Moving will happen, if the memory was dynamically allocated. The moved-to string will take over ownership of the moved-from string's buffer. The moved-from string will be set so it doesn't refer to that buffer anymore, and won't double-free it. – underscore_d Dec 23 '20 at 16:50
  • @underscore_d assuming memory was dynamically allocated, you mean `str_b = std::move(str_a)` statement will make `str_b` point to `str_a` internal `char* buffer`, and when you reassign `str_a = "abc"`, `str_a` internal `char* buffer` will now point to different memory location that contains `abc`? – Harry Dec 23 '20 at 16:56
  • @Harry No, as has already been said plenty times, more likely the moved-from string will have a `nullptr` to no buffer. If you see `abc`, maybe it uses the SSO. In any case, it won't point at the same dynamically allocated buffer as another `string` instance. Again, trust that stdlib implementors write efficient code for you and will move a dynamically allocated buffer if it exists, and won't then doubly refer to or free it. But I can't see any more ways that I can explain how they can do so. – underscore_d Dec 23 '20 at 17:08

1 Answers1

4

std::move is not moving anything. It cast its argument to an rvalue-reference. To actually move you need to pass str as rvalue-reference to move constructor(or move assignment) of another string as below.

std::string new_str(std::move(str));
print(str);
alex_noname
  • 26,459
  • 5
  • 69
  • 86
  • 2
    and even then, the move-constructor or move-assignment operator of the class might choose not to actually clear out the value, because by convention move does not have to (but they should document what they do either way) – underscore_d Dec 23 '20 at 15:37
  • Should they really document it ? Move operations should only put the moved-from object in a safe state to be destroyed. Users should not assume any specific state afterwards and should just stop using the moved-from object. – Nicolas Dusart Dec 23 '20 at 15:44
  • @NicolasDusart Does the Standard say that anyone implementing a move constructor or move-assignment operator must do that? If so, where, and does it require that of everyone or just its own Standard Library? Anyway, even if the Standard does set a default position on this, I thin I might argue that documenting it anyway is good. One can simply say 'I do what stdlib convention says' or 'I guarantee these extra things' or 'I do something totally different'. With good documentation like that, the many questions like this would not arise. – underscore_d Dec 23 '20 at 15:50
  • @underscore_d if they want to do a "move" operation which ends up in a determinate state, it is preferrable to offer it through a specific function. It is much better to stick with average C++ expectations about move constructors and assignment with no documentation than specific move constructors and assignment with very good documentation. People will not tend to read the documentation if they see a normal move assignment, it should just do what they expect it to do. – Nicolas Dusart Dec 23 '20 at 15:57
  • @NicolasDusart Where is said expectation documented, was my point - which I found [here](https://stackoverflow.com/a/12095473/2757035) - with my other point being that it still wouldn't hurt for someone else documenting their own move operations to say 'we do the same as the stdlib does here'. I agree that they should do the same kind of thing, but I don't think explicitly documenting it hurts. – underscore_d Dec 23 '20 at 15:59
  • @underscore_d my point is about that they apparently **should** document what they are doing. This is generally an implementation details how you steal the resources and still be valid for reassignment or destruction. It is probably tied to the current implementation if you can provide strong move safety or not and once you document it, you're a bit stuck then. I don't say it's bad to document it, but I wouldn't say that we **should** always document what your move operation do beside putting the object in a valid state for destruction and assignment but no other function. – Nicolas Dusart Dec 23 '20 at 16:11