-6

As seen in the following, I can still print "3" after the code printf("%d\n", p1.val);. Isn't the class p1 destroyed after std::move? code

The code is here:

class pointer
{
public:
    int val;
    int* my_pointer;

    pointer()
    {
        val = 0; 
        my_pointer = new int;
    }
    pointer(pointer&& other)
    {
        val = other.val;
        my_pointer = other.my_pointer;      
        other.my_pointer = nullptr;
    }
    ~pointer()
    {
        delete my_pointer;
        my_pointer = nullptr;
    }
};
int main()
{   
    pointer p1;
    p1.val = 3;
    *p1.my_pointer = 3;
    pointer p2(std::move(p1));
    printf("%d\n", p1.val);
    printf("%d\n", p2.val);
    return 0; 
}
Ned Ned
  • 21
  • 4
  • 3
    It's _undefined behavior_, hence nobody can tell why your code behaves like it does. – πάντα ῥεῖ Oct 13 '20 at 08:27
  • 4
    post code as text, not in images – phuclv Oct 13 '20 at 08:29
  • Please see [this answer](https://stackoverflow.com/a/7028318/402169), which explains how a moved-from object acts. Moving does not destroy the object, it puts it in an unspecified state. – tenfour Oct 13 '20 at 08:30
  • 4
    No, it's not destroyed. "Moving" does not move or destroy anything, and `std::move` is just a cast. All that happens to `p1` is the things that you wrote in the move constructor. – molbdnilo Oct 13 '20 at 08:31
  • `p1` is destroyed at the end of the scope where it's defined. Moving from it doesn't change that. – Pete Becker Oct 13 '20 at 13:01
  • 1
    This doesn't address the question, but assigning `nullptr` to `my_pointer` in the destructor doesn't accomplish anything. The object is going away, so there is no valid way to access its `my_pointer` field -- it no longer exists. – Pete Becker Oct 13 '20 at 13:03
  • Hmm. I have no idea why this question was closed. It's perfectly clear and detailed enough. What's the problem? – Elliott Oct 13 '20 at 13:51
  • 1
    Thanks a lot for your patience. It helped me understand the usage of std::move more deeply. – Ned Ned Oct 13 '20 at 13:57

1 Answers1

0

So move semantics is basically doing a shallow copy from an element that is about to die (go out of scope).

Say that we have object, dying_obj, that we know is about to go out of scope (and hence have its destructor called), we can change its type to an rvalue with std::move like so:

other_obj = std::move(dying_obj);

std::move is just a type cast. It has no run-time effect other than how its type is interpreted by the compiler for things like overloaded function resolution. In this case, constructor overloads.

It's important the understand that this really is just a type flag, and the convention is that it refers to an object out-of-scope for its caller.

BUT.. to hammer the point home that rvalues and lvalues are just little flags, we could abuse these flags for other purposes (don't ever do this!):

void increment (int & i)
{
    ++i;
}

void increment (int && i)
{
    --i;
}

int main ()
{
    // count up to 10:
    for (int i = 0; i < 10; increment(i))
        std::cout << i << " ";

    // count down from 20:
    for (int i = 20; i > 0; increment(std::move(i)))
        std::cout << i << " ";
}

The above code uses an lvalue as an "add" type and the rvalue as a minus type. Very dumb, but also perfectly logical, it works correctly and with defined behaviour.

Getting back to the real world: when you call a function/method with an rvalue object, like foo(std::move(obj)), you're signalling to the function that it can do as it likes with that object because it won't be used by the caller any more. That's the convention for what the signal means, but it's just a signal. You can certainly rely on the STL library to follow this convention everywhere, and probably every single library that's ever used by sane people.

Standard procedure is to do the move copy as efficiently as possible. Sadly we're forced to copy all the values, but we're not forced to copy all of the content that a pointer points to (this is where we do a shallow copy instead). For the pointers, we really must be careful to make sure that the destructor of dying_obj won't delete any of its allocations - there's lots of ways to do this, actually, but the simplest is just to change any pointers to nullptr after the copy. We could change the other values of dying_obj, but we don't need to and it takes time, so it's typical to leave it as it is.

So it's important to understand that the std::move(obj) doesn't invoke a destruction of the obj - it will still go out of scope at the same place, so at that point its destructor will be called - which, if we've done our job correctly, will have no effect at all.

As for your precise problem:

pointer p1;
p1.val = 3;
*p1.my_pointer = 3;
pointer p2(std::move(p1));
printf("%d\n", p1.val);
printf("%d\n", p2.val);

With std::move(p1) you're communicating to the constructor that p1 won't be used again, so it can pull out its contents (do the shallow copy), but then on the next line you show that you're still using p1:

printf("%d\n", p1.val);

So you're breaking your promise.

Elliott
  • 2,603
  • 2
  • 18
  • 35
  • 1
    Thanks a lot. I misunderstood the use of std::move. I thought that the moved object will be automatically destroyed and therefore cannot be used. – Ned Ned Oct 13 '20 at 13:59