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

Qt/C++下重复调用含while循环的函数旧实例内存未释放如何解决

问题核心原因

你的代码逻辑无法正常运行的根源有三点:

  • 单线程环境下,CamerasInitialize内部的死循环会完全阻塞Qt主线程的事件循环,按钮点击事件会卡在事件队列里得不到执行,直到循环退出才会触发槽函数,你原本设想的「先改标志再启动新函数」的执行顺序完全不会生效
  • 你没有在CamerasInitialize的循环退出后释放动态申请的内存、显存资源,本身就存在原生的内存泄漏问题
  • 槽函数直接调用新的CamerasInitialize会导致函数嵌套调用,前一次的函数上下文还没释放,新的就已经启动,进一步加重资源泄漏
解决方案

第一步:补充类成员变量

在Widget类的头文件中新增以下成员变量,用来标记运行状态和待切换的参数:

#include <QCoreApplication>
#include <QMetaObject>

// 类成员变量
volatile bool isNewCameraSelected = false; // 加volatile避免编译器优化缓存变量值
int m_pendingCamCount = -1; // 存储待启动的摄像头数量
bool m_isCamRunning = false; // 标记当前是否有摄像头任务在运行

第二步:修改按钮点击槽函数

槽函数仅负责修改状态标记,不直接启动新的初始化函数,避免阻塞事件循环:

void Widget::on_fourCameras_clicked(){
    isNewCameraSelected = true;
    m_pendingCamCount = 4;
}

void Widget::on_sixteenCameras_clicked(){
    isNewCameraSelected = true;
    m_pendingCamCount = 16;
}

第三步:修改CamerasInitialize函数

需要做三处核心修改:

  1. 循环内部加入Qt事件处理代码,让按钮点击事件能被及时响应
  2. 循环退出后补全所有动态申请资源的释放逻辑
  3. 当前任务完全退出后,通过Qt异步调用启动新的初始化任务,避免函数嵌套

修改后的完整CamerasInitialize代码如下:

void Widget::CamerasInitialize(int camNumber) {
    m_isCamRunning = true;
    isNewCameraSelected = false;
    m_pendingCamCount = -1;

    namedWindow( "Output", cv::WINDOW_OPENGL );
    setWindowProperty( "Output", WND_PROP_FULLSCREEN, WINDOW_FULLSCREEN );

    cv::cuda::GpuMat *inputFrames = new cv::cuda::GpuMat[camNumber];
    cv::cuda::GpuMat *inputFramesConverted = new cv::cuda::GpuMat[camNumber];
    cv::cuda::GpuMat *sphericalDistortionOutput = new cv::cuda::GpuMat[camNumber];
    cv::Ptr<cv::cudacodec::VideoReader> videoReader[camNumber];
    cv::cuda::PtrStepSz<int32_t> *d_ptrs = NULL;
    cv::cuda::PtrStepSz<int32_t> *h_ptrs = new cv::cuda::PtrStepSz<int32_t>[camNumber];

    cudaMalloc(&d_ptrs, camNumber * sizeof(cv::cuda::PtrStepSz<int32_t>));

    std::time_t timeBegin = std::time(0);
    int tick = 0;
    long frameCounter = 0;

    setCurrentMatrixCpuToGpu(camNumber);

    while (true)
    {
        // 新增:处理Qt事件队列,响应按钮点击等操作
        QCoreApplication::processEvents(QEventLoop::AllEvents);

        for(int i=0; i<camNumber; i++){
            videoReader[i]->nextFrame(inputFrames[i]);
            h_ptrs[i] = inputFrames[i];
        }

        if(isMovedCamera){
            pBackSub.release();
            pBackSub = cuda::createBackgroundSubtractorMOG();
            isMovedCamera = false;
        }

        cudaMemcpy(d_ptrs, h_ptrs, camNumber * sizeof(cv::cuda::PtrStepSz<int32_t>), cudaMemcpyHostToDevice);

        GpuMat outval;
        Mat outputCPU;

        if(camNumber == 4){
            arrayMult(d_ptrs, outval, cameraMatrixOnGpu4Camera, pixelW, pixelH);
        }else if(camNumber == 16){
            arrayMult(d_ptrs, outval, cameraMatrixOnGpu16Camera, pixelW, pixelH);
        }

        frameCounter++;

        std::time_t timeNow = std::time(0) - timeBegin;

        if (timeNow - tick >= 1)
        {
            tick++;
            frameCounter = 0;
        }

        outval = outval(Rect(cropPositionsX, cropPositionsY, cropPositionsW, cropPositionsH));
        cuda::resize(outval, outval, Size(SCREENRESOLUTION_WITDH, SCREENRESOLUTION_HEIGHT), 0, 0, INTER_AREA);

        imshow( "Output", outval);

        setMouseCallback("Output", CallBackFunc, NULL);

        if (waitKey(1) == 'q' || isNewCameraSelected)
        {
            isNewCameraSelected = false;
            break;
        }
    }

    // 新增:资源释放逻辑,必须补全否则会持续内存泄漏
    delete[] inputFrames;
    delete[] inputFramesConverted;
    delete[] sphericalDistortionOutput;
    delete[] h_ptrs;
    cudaFree(d_ptrs);
    // cv::Ptr类型的videoReader会自动释放资源,无需手动处理

    m_isCamRunning = false;

    // 新增:如果有待切换的摄像头任务,异步启动新的初始化,避免函数嵌套
    if(m_pendingCamCount > 0 && m_pendingCamCount != camNumber){
        QMetaObject::invokeMethod(this, [this](){
            CamerasInitialize(m_pendingCamCount);
        }, Qt::QueuedConnection);
    }
}
注意事项
  • volatile修饰isNewCameraSelected是必须的,否则编译器可能优化变量读取逻辑,导致循环内无法感知变量变化
  • QCoreApplication::processEvents尽量放在循环开头,保证事件能被及时响应
  • 异步调用QMetaObject::invokeMethod使用Qt::QueuedConnection可以保证当前函数完全退栈、资源完全释放后,再启动新的初始化任务,不会出现栈溢出或者资源抢占问题

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.10.06 03:48:00