2017-06-06 81 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