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

      

+3


source to share


2 answers


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.
};

      

+1


source


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.

+3


source







All Articles