这是我在另一个项目中遇到的问题的简化。
说我有以下课程:
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_ptr
个MyClass
个实例:
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。
答案 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
,因此会在以后销毁。