奇怪的对象分配行为 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.
};
我对对象分配有一种奇怪的行为。如果你能解释为什么这个作业是这样的,我将不胜感激。它已经花费了我很多时间。 我正在使用 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.
};