Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

std::move and construction/destruction of objects

As I remember, before any call of function, it allocs memory for function result and parameters at stack. Does that means if I have

T func()
{
    T a;
    return std::move(a);
}

I will still have copying, because it already was allocated memory for entire T? I also read at similar questions that

return a; 

is same as

return std::move(a);

So, I can't avoid copying to stack? Is rvalue a value at stack?

Will it be good way to use it somewhere:

T a = std::move(func());

So I will avoid copying result of function? Do I still have to create special move constructor and move operator=?

I tried to test it and got:

class Temp
{
public:
    Temp()
    {
        cout << "construct" << endl;
        i = 5;
    }
    ~Temp()
    {
        cout << "destruct" << endl;
    }
    Temp(const Temp& t)
    {
        i = t.i;
        cout << "copy construct" << endl;
    }
    Temp operator=(const Temp& t)
    {
        i = t.i;
        cout << "operator =" << endl;
        return *this;
    }
    int i;

};

Temp tempfunc1()
{
    Temp t1;
    t1.i = 7;
    return t1;
}

Temp tempfunc2()
{
    Temp t1;
    t1.i = 8;
    return std::move(t1);
}

int main()
{
        Temp t1;
        Temp t2;
        t2.i = 6;
        t1 = t2;
        cout << t1.i << endl;
        t1.i = 5;
        t1 = std::move(t2);
        cout << t1.i << endl;
        cout << "NEXT" << endl;
        t1 = tempfunc1();
        cout << t1.i << endl;
        cout << "NEXT" << endl;
        t1 = std::move(tempfunc1());
        cout << t1.i << endl;
        cout << "NEXT" << endl;
        t1 = tempfunc2();
        cout << t1.i << endl;
        cout << "NEXT" << endl;
        t1 = std::move(tempfunc2());
        cout << t1.i << endl;
}

with result:

construct
construct
operator =
copy construct
destruct
6
operator =
copy construct
destruct
6
NEXT
construct
copy construct
destruct
operator =
copy construct
destruct
destruct
7
NEXT
construct
copy construct
destruct
operator =
copy construct
destruct
destruct
7
NEXT
construct
copy construct
destruct
operator =
copy construct
destruct
destruct
8
NEXT
construct
copy construct
destruct
operator =
copy construct
destruct
destruct
8

As if there is nothing from using std::move. Or it works just if there exist speacial constructor and destructor?

Why "t1 = t2" calls both copy constructor and operator=?

Excuse me for such big heap of questions that may be very simple, even after reading much about that, maybe because my bad english, I still need explanations.

Thank you in advance.

like image 648
Arkady Avatar asked Dec 06 '25 23:12

Arkady


1 Answers

I've made some changes to your code which may help you understand and explore how this all works. I added an id element to each Temp object to make it easier to understand which object is which and also changed the signature of the operator= to return a reference rather than an object. First, here is the required include to make it into a complete program:

#include <iostream>
using std::cout;
using std::endl;

Next, here's the class which now includes a std::move constructor (with the &&) and a move = operator:

class Temp
{
    int id;
public:
    Temp() : id(++serial), i(5)
    {
        cout << "construct " << id << endl;
    }
    ~Temp()
    {
        cout << "destruct " << id << endl;
    }
    Temp(const Temp& t) : id(++serial), i(t.i)
    {
        cout << "copy construct " << id << " from " << t.id << endl;
    }
    Temp(Temp &&t) : id(++serial), i(t.i)
    {
        t.i = 5;  // set source to a default state
        cout << "move construct " << id << " from " << t.id << endl;
    }
    Temp &operator=(const Temp& t)
    {
        i = t.i;
        cout << "operator = " << id << " from " << t.id << endl;
        return *this;
    }
    Temp &operator=(Temp&& t)
    {
        i = t.i;
        t.i = 5;  // set source to a default state
        cout << "move operator = " << id << " from " << t.id << endl;
        return *this;
    }
    int i;
    static int serial;
};


int Temp::serial = 0;

Your functions are still the same, but see the comment

Temp tempfunc1()
{
    Temp t1;
    t1.i = 7;
    return t1;
}

Temp tempfunc2()
{
    Temp t1;
    t1.i = 8;
    return std::move(t1);   // not necessary to call std::move here
}

I've slightly altered main() to show how this all works:

int main()
{
    Temp t1;   
    Temp t2;   
    t2.i = 6;   
    t1 = t2;   
    cout << t1.i << endl;
    t1.i = 5;
    t1 = t2;
    cout << t1.i << endl;
    cout << "NEXT" << endl;
    t1 = tempfunc1();
    cout << t1.i << endl;
    cout << "NEXT" << endl;
    t1 = std::move(tempfunc1());
    cout << t1.i << endl;
    cout << "NEXT" << endl;
    t1 = tempfunc2();
    cout << t1.i << endl;
    cout << "NEXT" << endl;
    Temp t3(tempfunc1());
    cout << t3.i << endl;
    cout << "NEXT" << endl;
    Temp t4(t1);
    cout << t4.i << endl;
}

Finally here is the output:

construct 1
construct 2
operator = 1 from 2
6
operator = 1 from 2
6
NEXT
construct 3
move operator = 1 from 3
destruct 3
7
NEXT
construct 4
move operator = 1 from 4
destruct 4
7
NEXT
construct 5
move construct 6 from 5
destruct 5
move operator = 1 from 6
destruct 6
8
NEXT
construct 7
7
NEXT
copy construct 8 from 1
8
destruct 8
destruct 7
destruct 2
destruct 1

As you can see, with the fixed operator=, no temporary is created. Also once the move version of operator= is provided, temporary objects (as with those returned by your tempfunc1() and tempfunc2() functions) automatically use move semantics. Your tempfunc2() doesn't really need the std::move call within it. As you can see, that merely creates yet another temporary, so it hurts more than it helps. Finally note the in the creation of t3 that there is only a single object created and no temporary or move constructor is required.

update

It's probably worth noting that a move constructor doesn't really help much in this trivial class, but it can help a lot for classes that use allocated memory or are computationally expensive to create. In those cases, it's useful to remember that there are multiple steps required in a move constructor (or in the move version of operator=). Specifically, you must:

  1. release any resources used by the destination object, then
  2. move the resources from the source object to the destination object, then
  3. set the source object state such that the destructor may be called (for example, if the class had memory allocated, you should set the pointer to nullptr so that the destructor will operate correctly) and then finally,
  4. return *this.
like image 185
Edward Avatar answered Dec 09 '25 13:12

Edward



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!