Now that it's answered: Don't bother reading this question, it's a bit lengthy and probably not worth your time. There were bugs in my code, and that was the reason why the move constructor wasn't called. Check the answers for details. Keep in mind that RVO and NRVO (Named Return Value Optimization) might account for the calls not happening as you expect.
I expect the move ctor to be called for this line, but the copy ctor is called instead:
Ding d3 = d1 + d2;
The Ding class has a user-defined move ctor and on operator+ overload. The reason I expect the move ctor to be called is that the operator+ returns a temporary object, an rvalue reference, so the move optimization could happen.
Everything I'm writing here might be wrong as I'm a C++ beginner. Here's the code:
// Copied and modified code from here: https://stackoverflow.com/a/3109981
#include <iostream>
#include <cstring>
struct Ding {
char* data;
Ding(const char* p) {
std::cout << " ctor for: " << p << "\n";
size_t size = strlen(p) + 1;
data = new char[size];
memcpy(data, p, size);
}
~Ding() {
std::cout << " dtor for: " << data << "\n";
delete[] data;
}
Ding(const Ding& that) {
std::cout << " copy for: " << that.data << "\n";
size_t size = strlen(that.data) + 1;
data = new char[size];
memcpy(data, that.data, size);
}
Ding(Ding&& that) {
std::cout << " MOVE for: " << that.data << "\n";
data = that.data;
that.data = nullptr;
}
Ding& operator=(Ding that) {
std::cout << " assignment: " << that.data << "\n";
std::swap(data, that.data);
return *this;
}
Ding& operator+(const Ding that) const {
std::cout << " plus for: " << that.data << "\n";
size_t len_this = strlen(this->data);
size_t len_that = strlen(that.data);
char * tmp = new char[len_this + len_that + 1];
memcpy( tmp, this->data, len_this);
memcpy(&tmp[len_this], that.data, len_that + 1);
Ding * neu = new Ding(tmp);
return *neu;
}
};
void print(Ding d) {
std::cout << " (print): " << d.data << std::endl;
}
int main(void) {
std::cout << "putting a Ding on the stack\n";
Ding d1("jajaja");
std::cout << "calling print routine\n";
print(d1);
std::cout << "putting a second Ding on the stack\n";
Ding d2("nein");
// std::cout << "calling print routine\n";
// print(d2);
std::cout << "Putting a third Ding on the stack, init from + op ...\n";
std::cout << "... so expecting so see MOVE ctor used ...\n";
Ding d3 = d1 + d2;
// std::cout << "calling print routine\n";
// print(d3);
std::cout << "End of main, dtors being called ...\n";
}
The compiler invocations (on Win7) for VC2010 Express and MinGW (GCC 4.6) are as follows:
cl /nologo /W4 /EHsc /MD move-sem.cpp
g++ -std=c++0x move-sem.cpp -o move-gcc.exe
Both binaries produce the same output (sans order of destruction at end of program):
putting a Ding on the stack
ctor for: jajaja
calling print routine
copy for: jajaja
(print): jajaja
dtor for: jajaja
putting a second Ding on the stack
ctor for: nein
Putting a third Ding on the stack, init from + op ...
... so expecting so see MOVE ctor used ...
copy for: nein
plus for: nein
ctor for: jajajanein
dtor for: nein
copy for: jajajanein
End of main, dtors being called ...
dtor for: jajajanein
dtor for: nein
dtor for: jajaja
To recall what the question was after this long text: Why isn't the move constructor called for Ding d3 = d1 + d2;
?
I'm aware that there are other questions as to why move ctors aren't getting called but I cannot map their answers to this case.
- Why doesn't this C++0x code call the move constructor?
- Why is this code trying to call the copy constructor?
- Move constructor is not getting called in C++0x
Update
I changed the program as follows as per David Rodriguez' comments:
--- move-sem.cpp.orig 2012-03-17 17:00:56.901570900 +0100
+++ move-sem.cpp 2012-03-17 17:01:14.016549800 +0100
@@ -36,15 +36,14 @@
return *this;
}
- Ding& operator+(const Ding that) const {
+ Ding operator+(const Ding that) const {
std::cout << " plus for: " << that.data << "\n";
size_t len_this = strlen(this->data);
size_t len_that = strlen(that.data);
char * tmp = new char[len_this + len_that + 1];
memcpy( tmp, this->data, len_this);
memcpy(&tmp[len_this], that.data, len_that + 1);
- Ding * neu = new Ding(tmp);
- return *neu;
+ return tmp;
}
};
I then recompiled the program using the compiler invocations mentioned above and got an output where one copy (copy for: jajajanein
) was removed. I then tried the following line:
g++ -std=c++0x -fno-elide-constructors move-sem.cpp -o move-gcc.exe
And tata! Now I'm seeing the move ctor at work! ... But I think there's another error now, the output of that new move-gcc.exe
doesn't list the dtor invocations any more:
putting a Ding on the stack
ctor for: jajaja
calling print routine
copy for: jajaja
(print): jajaja
dtor for: jajaja
putting a second Ding on the stack
ctor for: nein
Putting a third Ding on the stack, init from + op ...
... so expecting so see MOVE ctor used ...
copy for: nein
plus for: nein
ctor for: jajajanein
MOVE for: jajajanein
dtor for:
Second Update
I replaced the bad operator+
with the following (possibly equally bad) code:
Ding& operator+=(const Ding & rhs) {
std::cout << " op+= for: " << data << " and " << rhs.data << "\n";
size_t len_this = strlen(this->data);
size_t len_that = strlen(rhs.data);
char * buf = new char[len_this + len_that + 1];
memcpy( buf, this->data, len_this);
memcpy(&buf[len_this], rhs.data, len_that + 1);
delete[] data;
data = buf;
return *this;
}
Ding operator+(const Ding & rhs) const {
Ding temp(*this);
temp += rhs;
return temp;
}
I also removed the following line from the destructor, and it stopped the program from terminating abnormally:
std::cout << " dtor for: " << data << "\n";
The move constructor is now being called when compiling with MSVC and with g++ -std=c++0x -fno-elide-constructors
.
new
there, if you want strings, usestd::string
) Once you have that clear and understand them, read on operator overloading and the recommended ways. Finally once you are comfortable with that, consider managing your own resources (you can even skip this part, in most cases you don't want to manage resources). – David Rodríguez - dribeasdata
member when I had (correctly, I believe) set tonullptr
in the move ctor. So the fix was to check(nullptr == data ? "NULL" : data)
before accessing the memory. I think the object was in an okay state for destruction, just the destructor was bad. – Lumicout << (void*)data
which will indicate if it is null or else do:cout << (data?data:"(null)"
to have the destructor print "(null)" if the pointer is not set rather than crash. – David Rodríguez - dribeas