I've been having difficulties understanding move constructors in C++. I have made a simple class with a default constructor, copy constructor, move constructor and destructor. Also, I have defined a function with two overloads, one accepts a reference to that class and one accepts an rvalue reference to that class. My test code is below.
#include <iostream>
class c {
public:
c() {
std::cout << "default constructor" << std::endl;
}
c(const c& s) {
std::cout << "copy constructor" << std::endl;
}
c(c&& s) {
std::cout << "move constructor" << std::endl;
}
~c() {
std::cout << "destructor" << std::endl;
}
};
void f(c& s) {
std::cout << "passed by reference" << std::endl;
}
void f(c&& s) {
std::cout << "passed by rvalue reference" << std::endl;
}
int main() {
c s1; // line 1
std::cout << "\n";
c s2(s1); // line 2
std::cout << "\n";
c s3(c()); // line 3
std::cout << "\n";
f(s1); // line 4
std::cout << "\n";
f(c()); // line 5
getchar();
return 0;
}
The output I'm getting is not what I was expecting. Below is the output I'm getting from this code.
default constructor
copy constructor
passed by reference
default constructor
passed by rvalue reference
destructor
I can understand the outputs from all lines except line 3
. On line 3
, which is c s3(c());
, c()
is an rvalue so I'd expect that s3
would be move constructed. But the output doesn't show that it is move constructed. On line 5
, I'm doing the same thing and passing an rvalue
to the function f()
and it indeed calls the overload that accepts an rvalue
reference. I'm very confused and would appreciate any information on this.
Edit: I am able to call the move constructor if I do c s3(std::move(c()));
but am I not already passing an rvalue to s3? Why would I need std::move
?
std::move is actually just a request to move and if the type of the object has not a move constructor/assign-operator defined or generated the move operation will fall back to a copy.
No move constructor is automatically generated.
Tagging our move constructor with "noexcept" tells the compiler that it will not throw any exceptions. This condition is checked in C++ using the type trait function: "std::is_no_throw_move_constructible". This function will tell you whether the specifier is correctly set on your move constructor.
If any constructor is being called, it means a new object is being created in memory. So, the only difference between a copy constructor and a move constructor is whether the source object that is passed to the constructor will have its member fields copied or moved into the new object.
The reason why you don't see any output from line 3 is that it declares a function, not a variable. This is due to an ambiguity called Most Vexing Parse.
Compare c s3(c())
with int foo(int ())
, which, thanks to implicit type adjustments, is the same as int foo(int (*f)())
.
To get around this, use brace initialisation (which was actually introduced in C++11 partly for this very reason):
c s3(c{});
// or
c s3{c()};
// or
c s3{c{}};
c s3(c());
does not construct any object. It is a function declaration. The function is called s3
, the return type is c
, and the parameter type is "pointer to function taking no parameters and returning c
". So you get no output, since a function declaration does not call any functions.
To avoid this sort of thing you can use list initialization, c s3{c()};
is probably more in line with what you had in mind.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With