2

我一直在用我無法真正理解的析構函數調用順序掙扎。C++ 11 shared_ptr對象的本地靜態成員變量銷燬順序

假設我們有如下定義:

#include <memory> 
#include <iostream> 

class DummyClass { 
    std::string name; 
public: 
    DummyClass(std::string name) : name(name) { std::cout << "DummyClass(" << name << ")" << std::endl; } 
    ~DummyClass() { std::cout << "~DummyClass(" << name << ")" << std::endl; } 
}; 


class TestClass { 
private: 

    static DummyClass dummy; 

    static DummyClass& objects() { 
     static DummyClass dummy("inner"); 
     return dummy; 
    } 
public: 
    TestClass() { 
     std::cout << "TestClass" << std::endl; 
     std::cout << "TestClass Objects is: " << &objects() << std::endl; 
    } 

    virtual ~TestClass() { 
     std::cout << "~TestClass Objects is: " << &objects() << std::endl; 
     std::cout << "~TestClass" << std::endl; 
    } 
}; 

DummyClass TestClass::dummy("outer"); 

現在,如果我實例化TestClass如下:

TestClass *mTest = nullptr; 

int main() { 
    mTest = new TestClass(); delete mTest; 
    return 0; 
} 

獲得的輸出是一個我希望:

DummyClass(outer) 
TestClass 
DummyClass(inner) 
TestClass Objects is: 0x.... 
~TestClass Objects is: 0x.... 
~TestClass 
~DummyClass(inner) 
~DummyClass(outer) 

但是,現在,如果我使用mTest的shared_ptr,如:

std::shared_ptr<TestClass> mTest; 

int main() { 
    mTest = std::make_shared<TestClass>(); 
    return 0; 
} 

產生的輸出是:

DummyClass(outer) 
TestClass 
DummyClass(inner) 
TestClass Objects is: 0x.... 
~DummyClass(inner) 
~TestClass Objects is: 0x.... 
~TestClass 
~DummyClass(outer) 

有人可以解釋爲什麼在的TestClass對象的析構函數結束之前被破壞DummyClass內部對象,在該特定情況? 我發現gcc 5.2.0使用-std = gnu ++ 11和clang 3.8.0與-std = C++ 11一致的行爲,但找不到引用此示例的任何特定文檔。

編輯:澄清:上述所有代碼都按照所提供的順序寫入相同的翻譯單元(* .cpp文件)。這是一個簡單的使用情況,我只有一個頭部類定義,它必須包含一個指向派生類對象的靜態列表this指針。這些指針是通過ctor添加的,並在達到dtor時被刪除。銷燬最後一個對象時觸發該問題。該列表保存在一個靜態方法裏面並通過它來訪問,以實現標題只有的目標。

+0

是定義'DummyClass的TestClass ::假(「外」);'和'的std :: shared_ptr的 MTEST;'在同一個翻譯單元(* .cpp文件),或者是不同的?如果同一個,哪一個是第一個? – aschepler

+0

在本例中,所有內容都位於同一翻譯單元中。順序如前所述,所以'DummyClass TestClass :: dummy(「outer」);'出現在'std :: shared_ptr之前 mTest;' –

回答

0

它與shared_ptr無關,但與模塊(cc文件)中全局變量的銷燬順序有關。規範聲明順序是未定義的,因此您不能假定靜態內部對象將在另一個全局對象之前或之前被銷燬。如果您需要一致的銷燬順序,我建議您明確處理它。

1

用於具有靜態存儲持續時間的所有對象(命名空間的成員,靜態類成員,並且在函數定義static對象)的規則是:

  • 如果整個初始化可被認爲是恆定的表達,這是初始化發生在其他事情之前。 (不適用於您的示例中的任何內容。)否則,

  • 命名空間成員和靜態類成員保證在調用同一個翻譯單元中的任何函數之前的某個點處開始初始化。 (在大多數的實現中,如果我們忽略了動態庫加載,所有這些發生main開始之前,在你的例子,因爲main是在同恩,我們知道main才發生。)

  • 命名空間成員和靜態類定義在同一TU中的成員按其定義的順序開始其初始化。

  • 對於在不同TU中定義的名稱空間成員和靜態類成員,初始化順序不能保證!

  • 函數中定義的靜態對象在第一次程序控制達到定義時(如果曾經)開始它們的初始化。

  • main返回或std::exit被調用時,具有靜態存儲持續時間的所有對象被銷燬,以相反的當每個完成其初始化。

所以在你的第二個例子:

  1. TestClass::dummy初始化開始。首先創建一個臨時std::string,然後調用DummyClass::DummyClass(std::string)

  2. DummyClass構造函數執行std::string複製,然後輸出"DummyClass(outer)\n"std::string臨時銷燬。初始化TestClass::dummy已完成。

  3. 初始化::mTest開始。這要求std::shared_ptr<TestClass>::shared_ptr()

  4. 構造函數shared_ptr設置智能指針爲null。初始化::mTest已完成。

  5. main開始。

  6. std::make_shared通話結束時創建一個TestClass對象,調用TestClass::TestClass()。此構造函數首先打印"TestClass\n",然後調用TestClass::objects()

  7. 裏面TestClass::objects(),本地對象dummy的初始化開始。再次創建一個臨時std::string,並調用DummyClass::DummyClass(std::string)

  8. DummyClass構造函數執行std::string複製,然後輸出"DummyClass(inner)\n"std::string臨時銷燬。初始化objects'dummy已完成。

  9. TestClass::TestClass()繼續,打印"TestClass Objects is: 0x ... \n"。動態TestClass對象的初始化已完成。

  10. 回到mainmake_shared函數返回一個臨時的std::shared_ptr<TestClass>。移動分配從返回的臨時移動到::mTest,然後臨時銷燬。請注意,儘管TestClass對象與::mTest相關聯,但它具有動態存儲持續時間,而不是靜態存儲持續時間,因此上述規則不適用於此。

  11. main返回。 C++開始用靜態存儲持續時間銷燬對象。

  12. 要完成初始化的最後一個靜態對象是TestClass::objects()的0123'本地位於上面的步驟8,因此它首先被銷燬。其析構函數體輸出"~DummyClass(inner)\n"

  13. 下一個對象完成初始化是在上面的步驟4 ::mTest,所以它的破壞開始下一個。該~shared_ptr析構函數最終摧毀所有的動態TestClass對象。

  14. TestClass::~TestClass()析構函數體首先調用TestClass::objects()

  15. TestClass::objects()中,我們遇到了一個已經被破壞的函數 - 局部靜態的定義,它是未定義行爲!很顯然,雖然,你什麼都不做,只是返回到以前包含dummy存儲的引用,它可能是你沒有用它做任何事情比把地址以外一件好事。

  16. TestClass::~TestClass()繼續,輸出"~TestClass Objects is: 0x ... \n"然後"~TestClass\n"

  17. ~shared_ptr析構函數爲::mTest解除分配關聯的內存並完成。

  18. 最後,在上面的第2步中,第一個完成初始化的靜態對象是TestClass::dummy,所以它最後被銷燬。的析構函數DummyClass::~DummyClass體輸出"~DummyClass\n"。該程序已完成。

所以,你的兩個例子之間最大的區別是,在TestClass破壞被延遲,直到shared_ptr被破壞 - 這其實並不重要,在事物的TestClass創建方案時。由於shared_ptr在第二個示例中的「內部」DummyClass之前創建,因此在「內部」對象消失後發生其銷燬,從而導致該未定義行爲。

如果這是你遇到了,需要解決實際問題的簡單化,你可以嘗試添加類似

class TestClass { 
    // ... 
public: 
    class ForceInit { 
     ForceInit() { TestClass::objects(); } 
    }; 
    // ... 
}; 

// ... 
TestClass::ForceInit force_init_before_mTest; 
std::shared_ptr<TestClass> mTest; 
+0

事實上,這只是一個相當複雜的情況的簡化: 我有一個頭只有類定義,它必須持有'this'指針的派生類對象的靜態列表。這些指針是通過ctor添加的,並在達到dtor時被刪除。顯然,問題在於銷燬最後一個對象時。 列表保持一個靜態方法裏面,並通過它訪問 –