从集合中删除元素时下标超出范围



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

假设我有以下类:

class MyClass {
public:
MyClass() {
std::cout << "MyClass constructedn";
Instances().insert(this);
}
~MyClass() {
std::cout << "MyClass destructedn";
Instances().erase(this);
}
static std::unordered_set<MyClass*>& Instances() {
static std::unordered_set<MyClass*> _instances;
return _instances;
}
};

它有一个静态unordered_set,用于跟踪类的现有实例。构造实例时,其地址将添加到集合中;当实例被销毁时,其地址将从集合中删除。

现在,我有另一个类,它的vectorshared_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));

一切都很好,直到程序终止,当我在MyClass的析构函数中执行Instances().erase(this)时收到读取访问冲突("矢量下标超出范围"(。

我想也许我试图多次从_instances中删除实例(因此couts(——但是,正如您所期望的那样,构造器只调用一次,析构函数只调用一次。我发现当这种情况发生时,_instances.size()等于0。奇怪的是,它等于在任何调用erase之前0。在从布景中删除任何东西之前,它是空的?!

在这一点上,我的理论是,这与程序终止时对象的破坏顺序有关。也许在调用MyClass的析构函数之前释放了静态_instances

我希望有人能够对此有所了解,并确认是否正在发生的事情。

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

如果重要,我正在使用 MSVC。下面是一个可执行示例。

这是发生的事情。首先构造类型InstanceContainer的全局变量,然后再输入main变量。函数静态变量_instances稍后在首次调用Instances()时创建。

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

有几种方法可以解决此问题。首先,您可以确保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,因此稍后销毁。

相关内容

  • 没有找到相关文章

最新更新