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函数
需要做三处核心修改:
- 循环内部加入Qt事件处理代码,让按钮点击事件能被及时响应
- 循环退出后补全所有动态申请资源的释放逻辑
- 当前任务完全退出后,通过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
相关产品推荐
相关产品推荐

