Strange assignment behavior of C ++ objects
I have strange behavior with object assignment. I would be very grateful if you can explain why this task works like this. It cost me a lot of time. I am using Visual Studio Enterprise 2017 (all default settings).
code:
#include "stdafx.h"
#include <iostream>
using namespace std;
class Test
{
public:
Test()
{
cout << "Constructor of " << this << endl;
}
~Test()
{
cout << "Destructor of " << this << endl;
}
};
int main()
{
cout << "Assignment 1" << endl;
auto t = Test();
cout << "Assignment 2" << endl;
t = Test();
int i = 0;
cin >> i;
return 0;
}
Output (before cin):
Assignment 1
Constructor of 006FFC9F
Assignment 2
Constructor of 006FFBC7
Destructor of 006FFBC7
Expected output (before cin):
Assignment 1
Constructor of 006FFC9F
Assignment 2
Destructor of 006FFC9F
Constructor of 006FFBC7
I wanted to write a test function that creates an object of my (template) class, runs some tests, then creates a new object and runs some more tests. The problem is that t holds the already destroyed object after the second assignment. I know I can just use dynamic allocation which results in the expected behavior, but why does this program behave differently?
Many thanks. Regards.
PS: Results are the same regardless of Release / Debug or 64/32 bit compilation
EDIT: More detailed example:
#include "stdafx.h"
#include <iostream>
using namespace std;
class Test
{
private:
float* val;
public:
Test()
{
val = new float;
cout << "Constructor of " << this << ", addr. of val: " << val << endl;
}
~Test()
{
cout << "Destructor of " << this << ", addr. of val: " << val << " --> DELETING VAL!" << endl;
delete val;
}
float* getVal() { return this->val; }
};
int main()
{
cout << "Assignment 1" << endl;
auto t = Test();
cout << "Assignment 2" << endl;
t = Test();
cout << "Val Address: " << t.getVal() << endl;
int i = 0;
cin >> i;
return 0;
}
Output (it contains the remote pointer at the end !!!):
Assignment 1
Constructor of 004FFBDC, addr. of val: 0072AEB0
Assignment 2
Constructor of 004FFB04, addr. of val: 00723928
Destructor of 004FFB04, addr. of val: 00723928 --> DELETING VAL!
Val Address: 00723928
source to share
Your confusion seems to be a mistaken expectation that the original object will be destroyed on assignment. For example, in this code:
cout << "Assignment 2" << endl;
t = Test();
This piece of code calls the move-assign statement. Since you haven't defined one, the compiler-generated default will more or less look like this:
Test & operator=(Test &&) {}
Notice how there is no constructor or (critically) destructor call in this code. The only constructors and destructors that will run are on a temporary object (which is what you observe in the actual output). The original object is not destroyed until the code goes out of scope; and why? It doesn't seem like you can stop using stack space before then.
Edit: something that might help you understand what's going on:
#include<iostream>
struct Test {
Test() {std::cout << "Constructed.\n";}
~Test() {std::cout << "Destructed.\n";}
Test(Test const&) {std::cout << "Copy-Constructed.\n";}
Test(Test &&) {std::cout << "Move-Constructed.\n";}
Test & operator=(Test const&) {std::cout << "Copy-Assigned.\n"; return *this;}
Test & operator=(Test &&) {std::cout << "Move-Assigned.\n"; return *this;}
};
int main() {
std::cout << "Test t;\n";
Test t; //Construction
std::cout << "Test t2(t);\n";
Test t2(t); //Copy-Construct
std::cout << "Test t3(std::move(t2));\n";
Test t3(std::move(t2)); //Move-Construct
std::cout << "Test t4 = t;\n";
Test t4 = t; //Copy Construct, due to Copy Ellision
std::cout << "Test t5 = Test();\n";
Test t5 = Test(); //Will probably be a normal Construct, due to Copy Ellision
std::cout << "t = t2;\n";
t = t2; //Copy Assign
std::cout << "t = Test();\n";
t = Test(); //Move Assign, will invoke Constructor and Destructor on temporary
std::cout << "Done! Cleanup will now happen!\n";
return 0;
}
The results obtained when compiled here :
Test t;
Constructed.
Test t2(t);
Copy-Constructed.
Test t3(std::move(t2));
Move-Constructed.
Test t4 = t;
Copy-Constructed.
Test t5 = Test();
Constructed.
t = t2;
Copy-Assigned.
t = Test();
Constructed.
Move-Assigned.
Destructed.
Done! Cleanup will now happen!
Destructed.
Destructed.
Destructed.
Destructed.
Destructed.
DOUBLE EDIT COMBO! :
As I mentioned in the comments, val
this is just a pointer. 8 bytes (on a 64-bit machine) allocated as part of the storage Test
. If you are trying to make sure it Test
always contains a valid value for val
that has not been removed, you need to do the Rule of Five (formerly known as the rule of three):
class Test {
float * val;
public:
Test() {val = new float;}
~Test() {delete val;
Test(Test const& t) {
val = new float(*(t.val));
}
Test(Test && t) {std::swap(val, t.val);}
Test & operator=(Test const& t) {
float * temp = new float(*(t.val)); //Gives Strong Exception Guarantee
delete val;
val = temp;
return *this;
}
Test & operator=(Test && t) {std::swap(val, t.val); return *this;};
float & get_val() const {return *val;} //Return by reference, not by pointer, to
//prevent accidental deletion.
};
source to share
FROM
auto t = Test();
you are actually creating two objects. First it is Test()
, which creates a temporary object. The second is a construction t
that is produced by copying. There is no assignment here, even if the operator is used =
, it is copy.
If you add a copy constructor to a class Test
similar to your constructor and destructor, you should see it clearly.
Concerning
t = Test();
here a temporary object is created with Test()
. This temporary object is then passed to the assignment operator (generated by the compiler) of the class Test
, and then the temporary object is quickly destroyed.
The object t
itself is not destroyed, it does not have to be what it is.
source to share