You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何检查std::future是否为空?C++地图块生成异常排查

地图生成器的std::future空值与内存异常问题

问题描述

我使用C++结合glm、Perlin噪声和std::future开发地图生成器,用于生成地图块。运行一段时间后出现了空std::future的情况,不清楚空future的含义及检查方法,希望了解如何检查std::future是否为空,同时找出代码中导致问题的错误。

异常信息

Unhandled exception at 0x7623D6C2 in VoxelEngine.exe:
Microsoft C++ exception: std::bad_alloc at memory location 0x00DDF290.

相关代码

void ChunkManager::update(float deltaTime)
{
    glm::vec3 cameraPosition = mainCamera->transform.getPosition();
    glm::i32vec3 newCameraChunk = cameraPosition / chunkSize;

    if (newCameraChunk != cameraChunk) {
        cameraChunk = newCameraChunk;
        loadChunks();
    }

    if (loading) {
        handleLoadingChunks();
    }
}

void ChunkManager::loadChunks()
{
    glm::i32vec3 minChunk(cameraChunk.x - visibleRange, 0, cameraChunk.z - visibleRange);
    float offset = Chunk::CHUNK_WIDTH * 2 * Block::BLOCK_SIZE;

    for (int x = 0; x < visibleRange*2; x++) {
        for (int z = 0; z < visibleRange*2; z++) {
            loadingChunks.push_back(std::async(std::launch::async, [&, x, z, minChunk, offset]() {return createChunk(x, 0, z, minChunk, offset, resourceManager); }));
        }
    }
    loading = true;
}

Chunk* ChunkManager::createChunk(int x, int y, int z, glm::i32vec3 minChunk, float offset, ResourceManager* resourceManager)
{
    Chunk* chunk = new Chunk(material, x + minChunk.x, y + minChunk.y, z + minChunk.z);
    chunk->InitializeChunk(noiseGenerator);
    chunk->CreateMesh(resourceManager);
    chunk->position(glm::vec3((x + minChunk.x) * offset, 0, (z + minChunk.z) * offset));
    return chunk;
}

void ChunkManager::handleLoadingChunks()
{
    bool allDone = true;
    for (auto& it : loadingChunks) {
        if (it.wait_for(std::chrono::seconds(0)) != std::future_status::ready) {
            allDone = false;
        }
    }

    if (allDone) {
        for (auto it : chunks) {
            delete it;
        }
        chunks.clear();

        for (auto& it : loadingChunks) {
            if (it.valid()) {
                Chunk* chunk = it.get(); // 此处经常出现空值问题
                chunk->FinalizeMesh();
                chunks.push_back(chunk);
            }
        }
        loadingChunks.clear();
        loading = false;
    }
}

class ChunkManager :
    public GameObject
{
public:
    ChunkManager(Material* material, Camera* camera, ResourceManager* resourceManager, NoiseGenerator* noiseGenerator);
    ~ChunkManager();

    void render(glm::mat4x4 projectionMatrix, glm::mat4x4 viewMatrix, glm::mat4x4 parentTransform);
    void update(float deltaTime);

private:
    std::vector<Chunk*> chunks;
    std::vector<std::future<Chunk*>> loadingChunks;

    glm::vec3 minCorner;

    glm::i32vec3 cameraChunk;

    void loadChunks();
    Chunk* createChunk(int x, int y, int z, glm::i32vec3 minChunk, float offset, ResourceManager* resourceManager);
    void handleLoadingChunks();

    float chunkSize;
    int visibleRange = 7;
    bool loading = false;
    Material* material;
    ResourceManager* resourceManager;
    NoiseGenerator* noiseGenerator;
};

补充排查信息

我找到了可能的错误原因,但不知道如何修复。似乎是std::future中创建的Chunk对象尝试返回主线程时,主线程内存不足。


问题解答

1. 检查std::future是否为空的方法

使用std::future::valid()成员函数,它会返回布尔值:

  • 返回true:表示该future关联了有效的异步任务,可安全调用get()获取结果。
  • 返回false:表示该future处于空状态(未关联任务、结果已被获取或已被移动)。你代码中已经用到了这个方法,逻辑是正确的。

2. 代码中的核心问题分析

(1)内存过载与泄漏

  • 重复加载导致内存堆积:相机每次移动都会触发loadChunks(),如果前一次加载未完成(loading仍为true),新的异步任务会不断被添加到loadingChunks中。当visibleRange=7时,单次生成14×14=196个Chunk,多次触发后内存会被快速耗尽,最终抛出std::bad_alloc。
  • 裸指针的内存管理风险:代码使用裸指针存储Chunk对象,若Chunk的析构函数未正确释放内部资源(比如Mesh顶点数据、GPU缓冲区等),会导致内存泄漏,日积月累耗尽系统内存。

(2)std::future空值的诱因

  • 系统资源不足:当系统无法创建新线程时,std::async可能返回无效的future,此时valid()返回false。
  • 异步任务内部抛出异常:如果createChunk中new Chunk或CreateMesh时内存不足,会抛出std::bad_alloc,该异常会被future捕获,调用get()时会重新抛出,容易被误判为future为空。

3. 修复方案

(1)避免重复触发加载

在update函数中,触发新加载前确保当前任务已完成:

if (newCameraChunk != cameraChunk) {
    // 等待当前加载完成再启动新任务,避免内存堆积
    while (loading) {
        handleLoadingChunks();
        std::this_thread::sleep_for(std::chrono::milliseconds(10)); // 避免阻塞主线程
    }
    cameraChunk = newCameraChunk;
    loadChunks();
}

(2)限制异步任务并发数

不要一次性创建大量线程,改用线程池控制并发数,或每次启动固定数量的任务。比如将std::launch::async改为std::launch::deferred结合手动调度,避免系统线程资源耗尽。

(3)优化内存管理

  • 使用智能指针:将std::vector<Chunk*>改为std::vector<std::unique_ptr<Chunk>>,std::future<Chunk*>改为std::future<std::unique_ptr<Chunk>>,避免手动管理内存导致的泄漏:
    std::unique_ptr<Chunk> ChunkManager::createChunk(int x, int y, int z, glm::i32vec3 minChunk, float offset, ResourceManager* resourceManager)
    {
        auto chunk = std::make_unique<Chunk>(material, x + minChunk.x, y + minChunk.y, z + minChunk.z);
        chunk->InitializeChunk(noiseGenerator);
        chunk->CreateMesh(resourceManager);
        chunk->position(glm::vec3((x + minChunk.x) * offset, 0, (z + minChunk.z) * offset));
        return chunk;
    }
    
  • 确保Chunk析构函数正确释放资源:检查Chunk类的析构函数,确保所有动态分配的内存、GPU资源都被正确释放。

(4)添加异常处理

在调用get()时捕获异常,避免程序崩溃:

for (auto& it : loadingChunks) {
    if (it.valid()) {
        try {
            auto chunk = it.get();
            if (chunk) {
                chunk->FinalizeMesh();
                chunks.push_back(std::move(chunk));
            }
        } catch (const std::bad_alloc& e) {
            std::cerr << "Chunk创建失败:内存不足 - " << e.what() << std::endl;
        } catch (...) {
            std::cerr << "Chunk创建失败:未知异常" << std::endl;
        }
    }
}

(5)优化Chunk替换逻辑

不要每次全量删除旧Chunk,对比新的可见范围,只删除不在范围内的Chunk,保留仍需要的Chunk,减少内存分配和释放的开销。


内容的提问来源于stack exchange,提问作者Marko Taht

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.19 00:07:06