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:
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.
#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){ /*...*/ }