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

C++ 奇怪的对象分配行为c++;,c++,class,constructor,visual-studio-2017,destructor,C++,Class,Constructor,Visual Studio 2017,Destructor,我在对象分配方面有一种奇怪的行为。如果你能解释一下为什么这个任务是这样的,我将不胜感激。这已经花费了我很多时间。 我正在使用Visual Studio Enterprise 2017(所有默认设置) 代码: 预期产量(至cin): 我想编写一个测试函数,创建我的(模板)类的一个对象,进行一些测试,然后创建一个新对象并进行更多测试。问题是t在第二次赋值后保存已经被破坏的对象。 我知道我可以使用动态分配,这会导致预期的行为,但是为什么这个程序的行为会有所不同呢 多谢各位。 问候 PS:结果相同,与发

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

代码:

预期产量(至cin):

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

多谢各位。 问候

PS:结果相同,与发布/调试或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;
}

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

如果您向
测试
类添加了一个类似于构造函数和析构函数的复制构造函数,您应该可以清楚地看到它


至于

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


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

您的困惑似乎是错误的期望,即分配发生时原始对象会被破坏。比如,在这个代码中:

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

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

#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;
}
双编辑组合

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


请看一下复制省略,这是自C++17以来在某些情况下必须执行的操作。
auto t=Test()不是赋值,而是初始化
您构造了一个
Test
的临时对象,将其传递给
t
operator=
,然后销毁该临时对象。你为什么认为这是个问题?如果您将print语句放在
操作符=
(为了完整起见,可能还需要复制构造函数),您会清楚地看到这种行为。“t在第二次赋值后保留已破坏的对象”不,它不保留新构造的对象,该对象已替换不再需要的对象。您的代码在任何时候都不需要同时存在两个
Test
对象。局部变量的生存期直到它们在其中声明的代码块结束。因此,在到达
main
末尾的
}
之前,您不应该期望看到调用
t
的析构函数。可能看不到副本,因为它可以省略。我认为混淆的是第二个赋值,而不是第一个赋值。使用添加的副本构造函数:Test(const Test&obj){cout@Fhnx-Ouch!如果没有该指针,将导致未定义的行为。当调用编译器生成的复制构造函数或赋值运算符时,它将复制指针,而不是它指向的内容。这将导致两个指针指向同一内存。现在想想当其中一个对象
删除
指针时,会发生什么情况另一个对象中的指针?如果没有复制省略,您的“第二个”将是“复制/移动构造”,而不是复制构造。(这意味着移动构造,除非根本不生成类的移动构造函数)我添加了一个更详细的浮动指针示例。指针被删除,值仍然被分配给对象?!@Fhnx按照我刚才所做的编辑查看发生了什么。另外,仅供参考,
delete
ing一个指针本身并没有清除指针的值,它只删除它所指向的内存。
float*
is 8堆栈上分配的字节,指向由4个字节组成的段,该段通过
new
分配,并通过
delete
销毁。无论
val
是否指向有效内存,都已销毁,或其他任何内容,都不会阻止您打印出
val
的值或重新分配它。谢谢您的详细说明这和@someotherprogrammerdude的响应解释了我的错误。所以我可以使用一个额外的复制构造函数。
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();
t = Test();
cout << "Assignment 2" << endl;
t = Test();
Test & operator=(Test &&) {}
#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;
}
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.
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.
};