奇怪的对象分配行为 c++

Strange object assignment behaviour c++

我对对象分配有一种奇怪的行为。如果你能解释为什么这个作业是这样的,我将不胜感激。它已经花费了我很多时间。 我正在使用 Visual Studio Enterprise 2017(所有默认设置)。

代码:

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

输出(最多cin):

Assignment 1
Constructor of 006FFC9F
Assignment 2
Constructor of 006FFBC7
Destructor of 006FFBC7

预期输出(最多 cin):

Assignment 1
Constructor of 006FFC9F
Assignment 2
Destructor of 006FFC9F
Constructor of 006FFBC7

我想编写一个测试函数来创建我的(模板)对象 class,进行一些测试,然后创建一个新对象并进行更多测试。问题是 t 在第二次赋值后持有已经被破坏的对象。 我知道我可以只使用动态分配来产生预期的行为,但为什么这个程序的行为不同?

非常感谢。 问候。

PS:结果相同,独立于Release/Debug或64/32位编译

编辑:更详细的示例:

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

输出(末尾有一个删除的指针!!!):

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

auto t = Test();

你实际上构造了两个对象。首先是 Test() 构造一个临时对象。二是t的构造,通过复制构造。这里没有赋值,即使使用了=运算符,也是拷贝构造。

如果你在Test class中添加一个类似于你的构造函数和析构函数的复制构造函数,你应该看清楚了。


至于

t = Test();

此处使用 Test() 创建了一个 临时 对象。然后将该临时对象传递给 Test class 的(编译器生成的)赋值运算符,然后立即销毁该临时对象。

对象t本身没有被破坏,它不应该被破坏,因为它是赋值的目的地。

你的困惑似乎是一种错误的期望,即原始对象在分配发生时被销毁。比如,在这段代码中:

cout << "Assignment 2" << endl;
t = Test();

这段代码调用了移动赋值运算符。由于您没有定义,编译器生成的默认值或多或少看起来像这样:

Test & operator=(Test &&) {}

请注意该代码中没有调用构造函数或(关键)析构函数。唯一要 运行 的构造函数和析构函数在临时对象上(这是您在实际输出中观察到的)。在代码超出范围之前,原始对象不会被销毁;为什么会这样?在那之前你不能停止使用堆栈 space。

编辑:可能有助于您了解正在发生的事情的内容:

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

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.

双重编辑组合!:

正如我在评论中提到的,val 只是一个指针。 8 个字节(在 64 位机器上)分配为 Test 存储的一部分。如果您试图确保 Test 始终包含未被删除的 val 的有效值,则需要实施 Rule of Five(以前称为三规则):

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