C++ 内存访问冲突仅发生在列表中,而不是相同操作的向量

C++ Memory Access Violation occurring with List only, not Vectors for Same Operations

我一直在创建一个 C++ Vulkan 程序,它使用子父 class 系统来管理资源并自动以正确的顺序释放它们。例如,考虑下面的代码 -

/**
 * Forward declaration of parent class
 */
class ParentResource;

/**
 * Represents a single child resource, all child resources must inherit from this class
 */
class ChildResource {
protected:

    /**
     * Creates an uninitialized resource, if this constructor is used then
     * the init method MUST be called if the derived object is ever initialized
     */
    ChildResource() {}

    /**
     * Adds a child resource to the list of child resources to be managed
     * @param parent The parent resource managing this resource
     */
    ChildResource(ParentResource& parent) : parentPtr(&parent) {
        parent.resources.push_back(this);
    }

    /**
     * Adds a child resource to the list of child resources to be managed
     * @param parent The parent resource managing this resource
     */
    virtual void init(ParentResource& parent) {
        parentPtr = &parent;
        if (std::find(parent.resources.begin(), parent.resources.end(), this) == parent.resources.end())
            parent.resources.push_back(this);
    }

    /**
     * Removes the child resource from the list of child resources to be managed
     */
    ~ChildResource() {
        if (parentPtr == nullptr) return;
        ParentResource& parent = *parentPtr;
        parent.resources.erase(std::remove(parent.resources.begin(), parent.resources.end(), this),
            parent.resources.end());
    }

    /**
     * The pointer to the parent
     */
    ParentResource* parentPtr = nullptr;

    friend class ParentResource;

public:

    /**
     * Frees resources if not already freed
     */
    virtual void freeResources() {
        if (parentPtr == nullptr) return;
        ParentResource& parent = *parentPtr;
        parent.resources.erase(std::remove(parent.resources.begin(), parent.resources.end(), this),
            parent.resources.end());
    }
};
/**
 * Forward declaration of child resource
 */
class ChildResource;

/**
 * Represents a single parent resource which manages several child resources
 */
class ParentResource {
protected:

    /**
     * All child resources being managed by this parent resource
     */
    std::vector<ChildResource*> resources;

    friend class ChildResource;
public:

    /**
     * Frees all children resources
     */
    virtual ~ParentResource() {
        freeResources();
    }

    /**
     * Frees all children resources
     */
    virtual void freeResources() {
        for (ChildResource* resource : resources) { // Memory Access Violation when using std::vector
            try {
                resource->freeResources();
            }
            catch (const std::exception& e) {
                console.error(e.what());
            }
        }
        resources.clear();
    }
};

此设置用于自动释放依赖于某些 vulkan 对象的资源,例如设备 class 将从父级 class 继承,以及依赖设备的东西(交换链、图形管线、着色器等)从父级设置为设备的子级继承。

以上大部分只是背景,这个问题并不是真正关于 Vulkan,而是 C++ list stl,因此我没有添加 Vulkan 标签。我的问题出现的地方是我在父 class.

的析构函数中不断遇到内存访问冲突

在调试时我发现列表中不知何故出现了一个 nullptr,这让我很困惑。更让我困惑的是,当我添加以下行时,崩溃仍然继续 -

if (resource == nullptr) continue;

然后将 Parent class 子列表从 std::list 更改为 std::vector,代码运行没有任何问题。

我不喜欢使用只是“神奇地工作”并且未能理解为什么这有效以及为什么列表没有的代码的概念。我相信链表是这种特定情况下的高级数据结构,并希望继续使用它们。

有人可以解释为什么 std::list 会导致内存访问冲突而 std::vector 不会吗?提前致谢!

TLDR - Lists don't support removing items during iterations but somehow vectors do?

正如 Andy Newman 在 中所述,问题是在循环时修改容器。我不确定它为什么用 std::vector 编译,但我能够在 MSVC 上复制相同的行为(原始 Post 使用 Apple Clang)。解决方案是简单地向 freeResources() 函数添加一个布尔参数,只有父 class 的 freeResources() 会设置为 false。如果设置为 false,此参数将停止修改列表。下面的代码示例 -


// Parent Class
virtual void freeResources() {
    for (ChildResource* resource : resources) { // Memory Access Violation when using std::vector
        try {
            resource->freeResources(false);
        }
        catch (const std::exception& e) {
            console.error(e.what());
        }
    }
    resources.clear();
}

// Child Class

/**
 * Frees resources if not already freed
 * @param remove Whether or not to remove this from the list
 */
virtual void freeResources(bool remove = true) {
    if (!remove || parentPtr == nullptr) return;
    parentPtr->resources.remove(this);
}

不需要显示额外的代码,这可能不是一个完全可重现的例子,对此我深表歉意(并且将来会避免),但它肯定足以发现问题,因为 Andy Newman 能够做到正在显示。

感谢所有尝试 help/took 花时间阅读本文的人!