When is move assignment/constructor is called


I am trying to understand when is the move constructor assignment is called:


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
#include <string>
#include <iostream>

struct A {
    int *number = new int{123};

    A() {
        std::cout << "default constructor\n";
    }

    A(A &&o) {
        std::cout << "move constructor\n";
        number = o.number;
        o.number = nullptr;
    }

    A &operator=(A &&other) {
        number = other.number;
        other.number = nullptr;
        return *this;
    }

    virtual ~A() {
        delete number;
    }
};

A createA() {
    return A();
}

void doSomething(A a) {
}

int main() {
    A a1{createA()}; // I expect move constructor call
    A a2;
    a2 = createA(); //I expect move assignment here

    doSomething(A{}); // I expect move constructor
  
}


None of those lines invoke any move operations and default constructor is always called. What am I missing in understanding of move concept?

Last edited on
On lines 36 and 40 the copy constructor would have been called, since you're passing copies. The compiler is eliding these copies, however, and rewriting your code into this:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
void createA(A *dst) {
    //Don't pay too much attention to the syntax. I can't recall how to call the
    //constructor explicitly on a piece of memory.
    dst->A::A();
}

void doSomething() {
    A a;
}

int main() {
    char a1[sizeof(A)];
    createA((A *)a1);
    //*(A *)a1 is now a valid A.
    
    //...
    
    doSomething();
  
}
It can be a bit tricky sometimes to understand when copy elision will be applied.

As for line 38, it does call the move assignment operator overload, but you forgot to print something in that function.
Last edited on
Both the move assignment op and the move ctor are leaking.
And there's no reason for a virtual dtor.
fixed logging error - assignment movement works.

Ok it is getting better, let's try this example
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
#include <string>
#include <iostream>
#include <utility>

struct A {
    std::string s;

    A() : s("test") {}

    A(A &&o) {
        std::cout << "move constructor\n";
        s = std::move(o.s);
    }

    A &operator=(A &&other) {
        s = std::move(other.s);
        std::cout << "move assigned\n";
        return *this;
    }
};

A f(A a) {
    std::cout << "return A\n";
    return a;
}

int main() {
    A a1 = f(A());
    A a2;
    a2 = f(A());
}


The result is:

return A
move constructor
return A
move constructor
move assigned


So, executing line 28 - `A()` is a rvalue(I assume) but it is not moved to a function, so is it copied, or it is optimized right to the function code. Then move constructor is invoked on return.
executing line 30 - so the same happens but move assignment is invoked at the end, what is happening here? Does move constructor, moves data to a tmp object, and then because of assignment we move it again to final place?
is it copied, or it is optimized right to the function code
The only way to know is to add a copy constructor. Note that copy elision is not exactly an optimization, since it changes the semantics of the code.

Does move constructor, moves data to a tmp object, and then because of assignment we move it again to final place?
That seems to be the case, although I don't get why it needs to do that. Maybe because at the time it would have performed the assignment the source is already out of scope?
By the way, to prevent accidentally performing moves inconsistently it's always a good idea to implement the move constructor in terms of the move assignment operator:
1
2
3
4
5
6
A(A &&other){
    //Make sure your object is valid, as required by the assignment.
    //Mainly, all naked pointers should be initialized.
    *this = std::move(other);
}
A &operator=(A &&other){ /*...*/ }
Topic archived. No new replies allowed.