2017-07-27 53 views
1

這是我在另一個項目中遇到的問題的簡化。從集合中刪除元素時,下標超出範圍

說我有下面的類:

class MyClass { 

public: 
    MyClass() { 
     std::cout << "MyClass constructed\n"; 
     Instances().insert(this); 
    } 
    ~MyClass() { 
     std::cout << "MyClass destructed\n"; 
     Instances().erase(this); 
    } 

    static std::unordered_set<MyClass*>& Instances() { 
     static std::unordered_set<MyClass*> _instances; 
     return _instances; 
    } 

}; 

它有它使用跟蹤之類的現有實例的靜態unordered_set。當一個實例被構造時,它的地址被添加到集合中;當一個實例被銷燬時,它的地址將從集合中刪除。

現在,我有了包含MyClass實例shared_ptrvector是另一個類:

struct InstanceContainer { 
    std::vector<std::shared_ptr<MyClass>> instances; 
}; 

一個關鍵點在這裏是有這個類以上main的全局實例。這似乎是問題的一部分,因爲在main中聲明該類不會產生問題。

裏面的main,我這樣做(說的InstanceContainer全局實例被稱爲container):

container.instances.emplace_back(std::shared_ptr<MyClass>(new MyClass)); 

一切都很好,直到節目結束,當我得到一個讀訪問衝突(「矢量標超出範圍「)Instances().erase(this)MyClass的析構函數中執行。

我以爲也許我試圖從_instances多次刪除實例(因此是cout s) - 但是,構造函數只調用一次,並且析構函數只被調用一次,就像您期望的那樣。我發現當發生這種情況時,_instances.size()等於0。奇怪的是,在調用erase之前,它等於0。在任何東西被刪除之前,它是空的?!

我的理論在這一點上是與程序終止時對象被破壞的順序有關。也許在調用MyClass的析構函數之前釋放靜態_instances

我希望有人能夠對此有所瞭解,並確認是否發生了這種情況。

我現在的解決方法是在試圖擦除之前檢查_instances.size()0。這安全嗎?如果不是,我還能做什麼?

如果重要,我使用MSVC。這是一個executable example

回答

1

這是發生了什麼事。首先構造InstanceContainer類型的全局變量,然後輸入main。稍後創建函數靜態變量_instances,此時將首次調用Instances()

在程序關閉時,這些對象的析構函數以相反的構造順序調用。因此,_instances首先被銷燬,然後InstanceContainer,然後InstanceContainer銷燬其共享指針的向量,該向量繼而在仍然在向量中的所有對象上運行~MyClass,該對象又在已經銷燬的_instances上調用_instances.erase()。因此,您的程序通過訪問終生已結束的對象來展示未定義的行爲。

有幾種方法可以解決這個問題。其一,您可以確保在main返回之前InstanceContainer::instances爲空。不知道這是多麼可行,因爲你從來沒有解釋InstanceContainer在你的設計中扮演什麼角色。

兩個,你可以在堆上分配_instances,只是漏呢:

static std::unordered_set<MyClass*>& Instances() { 
    static auto* _instances = new std::unordered_set<MyClass*>; 
    return *_instances; 
} 

這將讓它活着通過全局對象的破壞。

三,你可以把這樣的事情InstanceContainer全局變量的定義之前:

static int dummy = (MyClass::Instances(), 0); 

這將確保_instances較早創建的,因此後來被毀。