从集合

时间:2017-07-27 09:43:36

标签: c++11 smart-pointers unordered-set

这是我在另一个项目中遇到的问题的简化。

说我有以下课程:

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,用于跟踪类的现有实例。构造实例时,将其地址添加到集合中;当一个实例被销毁时,它的地址将从集合中删除。

现在,我有另一个类vector shared_ptrMyClass个实例:

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)&#39;中执行MyClass时,我获得了读访问权限违规(&#34;向量下标超出范围&#34;)析构函数。

我想也许我试图多次从_instances擦除实例(因此cout s) - 但是,构造函数只被调用一次,而析构函数只被调用一次,正如你所期望的那样。我发现,当发生这种情况时,_instances.size()等于0。奇怪的是,在调用0之前,它等于erase。在从集合中删除任何内容之前,它是空的吗?!

我的理论是,这与程序终止时对象被破坏的顺序有关。在调用_instances的析构函数之前,可能会释放静态MyClass

我希望有人能够对此有所了解,并确认这是不是发生了什么。

我现在的解决方法是在尝试删除之前检查_instances.size()是否为0。这样安全吗?如果没有,我还能做什么?

如果重要,我使用的是MSVC。这是executable example

1 个答案:

答案 0 :(得分:1)

这是发生了什么。在输入InstanceContainer之前,首先构造main类型的全局变量。函数静态变量_instances是在第一次调用Instances()时创建的。

在程序关闭时,将按照与构造相反的顺序调用这些对象的析构函数。因此,首先销毁_instances,然后销毁InstanceContainer,然后{@ 1}}会破坏其共享指针的向量,而共享指针又会在仍然在向量中的所有对象上运行~MyClass _instances.erase()已经被破坏的_instances。因此,您的程序通过访问其生命周期已结束的对象来展示未定义的行为。

有几种方法可以解决这个问题。一,在InstanceContainer::instances返回之前,您可以确保main为空。不知道这是多么可行,因为你从未解释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,因此会在以后销毁。