C++创建子类对象数组并调用子类成员函数的异常问题排查
看起来你遇到的问题是调用子类xiAPIplusCameraOcv的GetNextImageOcvMat()成员函数时,对象意外触发析构函数,导致相机被关闭。结合你的代码和描述,我整理了几个最可能的原因和对应的解决思路:
1. 未捕获的异常触发栈展开
最常见的原因是GetNextImageOcvMat()函数内部抛出了非xiAPIplus_Exception类型的异常,比如OpenCV的cv::Exception或者标准库异常,而你的catch块只捕获xiAPIplus_Exception,导致异常向上传播,触发栈展开——此时try块中的局部变量(包括cam数组)会被销毁,所有相机对象的析构函数都会被调用,表现为你看到的“调用函数后析构触发”。
解决方法
扩展catch块,捕获更多类型的异常,同时打印详细错误信息,定位问题根源:
catch (xiAPIplus_Exception& exp) { printf("xiAPI Error:\n"); exp.PrintError(); } catch (cv::Exception& exp) { printf("OpenCV Error:\n%s\n", exp.what()); } catch (std::exception& exp) { printf("Standard Error:\n%s\n", exp.what()); } catch (...) { printf("Unknown Error occurred!\n"); }
2. 意外的对象拷贝导致临时对象销毁
如果父类xiAPIplus_Camera包含不可拷贝的资源(比如相机句柄、指针),而子类xiAPIplusCameraOcv没有显式禁用拷贝构造和赋值操作,可能在某些隐式场景下触发对象拷贝(虽然你的代码中没有直接拷贝,但某些函数调用可能隐含拷贝),临时拷贝的对象被销毁时会打印析构日志,让你误以为原对象被销毁。
解决方法
在子类中显式禁用拷贝构造和赋值操作,避免意外拷贝:
class xiAPIplusCameraOcv : public xiAPIplus_Camera { public: xiAPIplusCameraOcv(); virtual ~xiAPIplusCameraOcv(); cv::Mat GetNextImageOcvMat(); cv::Mat ConvertOcvMat(xiAPIplus_Image * input_image); // 禁用拷贝构造和赋值 xiAPIplusCameraOcv(const xiAPIplusCameraOcv&) = delete; xiAPIplusCameraOcv& operator=(const xiAPIplusCameraOcv&) = delete; private: // ... 原有成员 };
3. 栈内存损坏导致对象意外销毁
你使用了栈上的对象数组xiAPIplusCameraOcv cam[5];,如果代码中存在数组越界、非法内存访问等问题,可能破坏栈结构,导致对象的生命周期被错误触发(比如提前调用析构函数)。
解决方法
改用堆分配的对象,避免栈内存问题,同时更灵活地管理相机对象:
int main(int argc, char* argv[]) { try { int n = xiAPIplusCameraOcv().GetNumberOfConnectedCameras(); std::vector<std::unique_ptr<xiAPIplusCameraOcv>> cam; cam.reserve(n); char sn[100]; for (int id = 0; id < n; id++) { printf("Opening %d. camera ...\n", id + 1); auto camera = std::make_unique<xiAPIplusCameraOcv>(); camera->OpenByID(id); camera->GetSerialNumber(sn, 100); cam.push_back(std::move(camera)); } for (int exp = 0; exp < n; exp++) { cam[exp]->StartAcquisition(); cv::Mat cv_mat_image = cam[exp]->GetNextImageOcvMat(); cv::imwrite(save_at, cv_mat_image); cam[exp]->StopAcquisition(); } // ... 后续代码 } // ... catch块 }
4. 添加调试信息定位问题
为了明确是哪个对象被销毁,你可以在析构函数和主函数中添加地址打印,确认是否是原相机对象被销毁:
修改析构函数
xiAPIplusCameraOcv::~xiAPIplusCameraOcv(){ printf("Closing xiAPIplus_Ocv camera at address: %p\n", this); cv_mat_image_.release(); }
修改主函数循环
for (int exp = 0; exp < n; exp++) { printf("Processing camera at address: %p\n", &cam[exp]); cam[exp].StartAcquisition(); cv::Mat cv_mat_image = cam[exp].GetNextImageOcvMat(); cv::imwrite(save_at, cv_mat_image); cam[exp].StopAcquisition(); }
对比打印的地址,如果析构的地址和当前处理的相机地址一致,说明原对象确实被意外销毁;如果地址不同,则是临时对象的析构,无需担心。
内容的提问来源于stack exchange,提问作者campy

