2017-06-06 93 views
3

我有一个奇怪的行为与对象分配。如果你能解释为什么这项任务能像这样工作,我将非常感激。这花了我很多时间。 我正在使用Visual Studio Enterprise 2017(所有默认设置)。奇怪的对象分配行为C++

代码:

#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 

我想写一个测试功能,创建我的(模板)类的对象,做一些测试,然后创建一个新对象并做更多的测试。问题在于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; 
} 

输出(它保持在一个已删除的指针结束!!!):

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 
+1

看一看复制椭圆,自C++ 17以来必须在某些情况下执行。 –

+2

'auto t = Test();'不是一个赋值,而是一个初始化。 – Jarod42

+1

在't = Test();'你构造一个Test的临时对象,把它传递给't'的'operator =',然后销毁这个临时对象。你为什么认为这是一个问题?如果你把print语句放在'operator ='中(为了完整起见,也许是copy-constructor),你会清楚地看到这种行为。 –

回答

1

你的困惑似乎是一个错误的预期,原来的对象被销毁作业时发生。像这样的代码:

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

这段代码调用move-assign操作符。既然你没有定义一个,由编译器生成的默认之一是更多或更少的打算是这样的:

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

结果时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编辑COMBO!

正如我在评论中提到的,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. 
}; 
+0

我添加了一个更详细的例子和一个浮点指针。指针被删除并且值仍然分配给该对象? – Fhnx

+1

@Fhnx按照我刚刚做的编辑看看发生了什么。另外,仅供参考,删除一个指针不会清除指针本身的值,它只会删除它指向的内存。 'float *'是在堆栈中分配的8个字节,指向一个4字节的段,它被分配'new'并且被'delete'破坏。无论val是否指向有效内存,是否已被销毁或什么,都不会阻止您打印val的值或重新分配它。 – Xirema

+0

谢谢你的详细例子。这和@Some其他程序员老兄的回应解释了我的错误。所以我应该罚款一个额外的复制构造函数。 – Fhnx

3

随着

auto t = Test(); 

你实际上构造了两个对象。首先是构成临时对象的Test()。第二个是t的制作,它是通过拷贝制作制作的。无法在这里正在作出分配,即使使用了=运营商,它的复制建设。

如果添加了一个拷贝构造函数的Test类类似于您的构造函数和析构函数,你应该清楚地看到它。


至于

t = Test(); 

这里临时对象与Test()创建。然后,该临时对象被传递到Test类的(编译器生成的)赋值运算符,然后将临时对象被迅速破坏。

对象t本身不破坏,它不应该是因为它是赋值的对象。

+1

可能不会看到副本,因为它可以被删除。 – NathanOliver

+1

我认为第二项任务是混乱,而不是第一项。 – Xirema

+0

使用附加的拷贝构造函数: \t Test(const Test&obj){<< this << endl;}的拷贝构造函数 \t} 我的输出保持不变: 分配1 009BFEBF 分配的构造函数2 009BFDE7 的009BFDE7 – Fhnx