Why the move constructor is not used?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#include <iostream>

struct X
{
    int * data;
    
    X() : data(new int[3]) {std::cout<<"default_constructor\n";}
    ~X() { delete [] data; std::cout<<"destructor\n";}
    
    X( const X & other) : data(new int[3]) {
        std::copy(other.data, other.data+3, data);
        std::cout << "copy_constructor\n";
    }
    X( X && other) : data(other.data) {
        std::cout << "move_constructor\n";
    }
};


int main()
{
    X var( X{} );
}

I expect:
defaoult_constructor
move_constructor
destructor

But my output is:
default_constructor
destructor

Why did the move constructor not invoked?
Last edited on
Because of copy elision. In short, C++ implementations are allowed to not call copy/move constructors for objects when they'd be initialized from some nameless temporary of the same type. This happens even if those constructors have side effects.

This is desirable behavior for many, many circumstances, but there are cases where you might want to turn it off. The way to do so, AFAIK, varies with compiler. For g++ and clang++, I believe the flag is -fno-elide-constructors. There are also probably attributes and pragmas to do so as well. Doing that will get you the following output:
default_constructor
move_constructor
destructor
destructor


-Albatross

EDIT: Clarification, and added actual output.
Last edited on
If you build debug versions of your code, you will see what you expected in the same fashion (and reason) as @Albatross' point.

Mh, so I wonder why (with the flag) the destructor of the temporary object will be called. I thought that the move constructor moves the content of a temporary to the newly created object. But if the temporary's data will get destroyed, that's bad.
Last edited on
The move constructor does not make the moved-from object cease to exist. It needs to leave that object in a valid state. What that state is, the standard doesn't say, but it should be destroyable in a way that makes sense.

And yes, you do presently have a problem with your move constructor. :D

-Albatross
Would this be a valid solution?
1
2
3
4
    X( X && other) : data(other.data) {
        other.data = nullptr;
        std::cout << "move_constructor\n";
    }

Yep, assuming that nullptr is a valid value for your data member variable (by your own designs).

What I mean by that is that... well, if you have any functions that blindly assume that data points to an array of 3 ints, then those functions are going to be in for a rude surprise.

-Albatross
>... well, if you have any functions that blindly assume that data points to an array of 3 ints, then those functions are going to be in for a rude surprise.

That should never happen, because the move-constructor will then and only then be invoked if X other is an r-value and thus nevermore could get used after passing to the move constructor.
nuderobmonkey wrote:
the move-constructor will then and only then be invoked if X other is an r-value

Sure, but...
and thus nevermore could get used after passing to the move constructor

...not if you do something like some_x = std::move(another_x); EDIT: X some_x{ std::move(another_x) };.

-Albatross
Last edited on
That would be like taking an axe and bashing it into my leg. XD
The point is that it's a not-uncommon circumstance under which a move constructor would be called, and the moved-from object doesn't expire immediately after. It's something to consider, especially if your code is going to be reviewed/used by other people.

-Albatross
But some_x = std::move(another_x); is a job for the move-assignment operator, isn't it?
Whoops, I typed without really thinking about the details of what I was typing. You're right. X some_x = std::move(another_x); would be a different story, though.

-Albatross
Last edited on
Topic archived. No new replies allowed.