3

I am reading this post: What are move semantics?

Note that the example given in that post for move constructor was:

string(string&& that)  
{
    data = that.data;
    that.data = nullptr;
}

I found it confusing when we use string a(x+y) to construct a new string. Since the result of x+y is a temporary variable, it gets destroyed very soon. That would mean copying over the pointer (data = that.data) would indeed be copying over a dangling pointer, after the original data (which should be stored in the stack frame of x+y that gets cleaned up after the function call finishes) is destroyed. It seems that setting that.data to nullptr won't help, as the stack frame gets cleaned up anyways.

Can anyone explain why this is not an issue? And how does c++ actually handles such situations?

Community
  • 1
  • 1
OneZero
  • 11,556
  • 15
  • 55
  • 92
  • Moving an object usually alters the target and source object (the target steals data and leave the source with nothing - maybe a swap of data) –  Jan 06 '16 at 19:37
  • `x+y` creates a temporary string that holds a pointer `data` to memory on the heap which contains the relevant data. If the temporary goes out of scope, the memory it owned on the heap gets deleted. But the move constructor you quoted alters the temporary in a way that it does not own that memory anymore - so it will go out of scope without actually touching its contents. Instead, the newly created `a` now owns that memory (its `data` points to the data, originally owned by `x+y`). – Pixelchemist Jan 06 '16 at 19:51

3 Answers3

5

When you do:

string a(x + y);

it is equivalent to:

string temp(x + y);
string a(move(temp));
//destroy temp

The relevant code for the move constructor that you quote takes a as this and that as temp, so it could be inlined as:

string temp(x + y);
string a(/*uninitialized*/);
a.data = temp.data;
temp.data = nullptr;
//destroy temp

As you can see, the temp.data is the one that gets nulled, so the destructor of temp becomes a no-op and the actual data survives inside a, as expected.

It looks like your confusion comes from the origin of data. In the simplest string implementation, string::data is always a dynamically allocated block of memory:

string(const char *str)
{
    size_t len = strlen(str);
    data = new char[len + 1];
    strcpy(data, len);
}   
~string()
{
    delete[] data;
}

Even if the string is allocated on the stack, such as temp and a and maybe even x and y their data memory block is dynamic.

True, real-world string implementations usually do the non-dynamic-short-string optimization. But if you do that, then the move constructor (and any other member function) will be a bit more complicated.

rodrigo
  • 94,151
  • 12
  • 143
  • 190
  • @OneZero: Too long to explain in a comment. Please see my updated answer. – rodrigo Jan 06 '16 at 19:44
  • What if it's not a `string`, but a customized object type that just has `int data[5]` (instead of using `new`)? In that case, does it mean that we can not simply copy `data` pointer? – OneZero Jan 06 '16 at 21:23
  • @OneZero In that case `data` is not a pointer but an array, and the move constructor is simply not useful. – rodrigo Jan 06 '16 at 21:25
2

Since the result of x+y is a temporary variable, it gets destroyed very soon. That would mean copying over the pointer (data = that.data) would indeed be copying over a dangling pointer

Nope. You copy the pointer so now the new string has the data and then you set the pointer of the temporary to nullptr so when the temporary is destroyed it does not delete the string data.

You can see how this is working in this little example

#include <iostream>

struct Foo
{
    int * f;
    Foo(int size) : f(new int[size]) 
    { 
        for (int i = 0; i < size; i++)
            f[i] = i; 
    }
    Foo() : f(nullptr) {}
    ~Foo() { delete [] f; }
};

int main()
{
    int size = 10;
    Foo b;  // b is empty
    {
        Foo f(size);  // now f has an of size 10
        // if we now swap the contents like the move operation does
        b.f = f.f;
        f.f = nullptr;
    } // f goes out of scope and ~Foo() is called
    // now here b.f is valid as delete on nullptr did nothing
    for (int i = 0; i < size; i++)
            std::cout << b.f[i] << " ";
}

Live Example

NathanOliver
  • 171,901
  • 28
  • 288
  • 402
0

In the case of string data is stored on the heap, not in stack frames. The "string" object only contains the pointer and maybe some additional data (e.g. the length). Thus, the move constructor you show effectively "robs" the that object of the ownership of its data. The second line, assigning nullptr to the data pointer of the temporary, is necessary in order to avoid the destructor of that same temporary from deleting the data we stole (because calling delete on nullptr is guaranteed to have no effect).

Javier Martín
  • 2,537
  • 10
  • 15