C++14自定义结构体场景智能指针赋值与C风格转换问题
问题背景
基于C++14开发程序时,需要分配内存获取驱动相关信息,流程为:调用可返回信息存储位置指针的接口后,绑定自定义结构体数组到对应内存地址即可访问目标数据。原有基于new/delete的实现可正常运行,为规避内存泄漏风险,计划改用智能指针实现自动内存管理。
原有可运行实现代码
首先定义两个业务相关结构体:
// 用于获取指针信息的自定义结构体 typedef struct tag_SelectData { short iCount; // 可选数据的数量 short iSize; // 单个可选数据的字节长度 int *lpData; // 可选数据数组指针 // *应用必须为该成员分配缓冲区,并将缓冲区地址赋值给该参数 // 如果lpData为0,函数仅输出iCount和iSize字段 } SELECTDATA; // 存储目标驱动信息的结构体 typedef struct tag_MediaTypeRange_4 { DWORD dwVersion; // 结构体版本,必须为4 DWORD dwSize; // 结构体大小 long iMaxWid; // 最大宽度 long iMaxHig; // 最大高度 long iMinWid; // 最小宽度 long iMinHig; // 最小高度 long iTypeID; // 媒体类型ID } MEDIATYPERANGE_4;
原始业务逻辑代码:
/*** 原有业务实现 ***/ SELECTDATA selectData; selectData.iCount = 0; selectData.iSize = 0; selectData.lpData = NULL; GetSelectDataRange(CMD_MEDIATYPE, &selectData); // 调用接口获取内存指针 MEDIATYPERANGE_4 *arr = new MEDIATYPERANGE_4[selectData.iCount]; // 分配数组内存 arr = (MEDIATYPERANGE_4 *)selectData.lpData; // 将接口返回的指针赋值给数组指针 // 遍历数组输出结果 for (size_t i = 0; i < selectData.iCount; i++) { std::cout << arr[i].iMaxWid << std::endl; std::cout << arr[i].iMinWid << std::endl; std::cout << arr[i].iMaxHig << std::endl; std::cout << arr[i].iMinHig << std::endl; }
改造遇到的问题
改造为智能指针实现时,在指针赋值环节编译失败:
首先尝试创建持有自定义结构体数组的shared_ptr实例:
// 创建自定义结构体类型的智能指针 std::shared_ptr<MEDIALAYOUTRANGE_4[]> mediaArr(new MEDIALAYOUTRANGE_4[selectData.iCount]());
已知后续可通过mediaArr.get()[i]的方式访问数组元素,但创建智能指针时仅完成了自身内存分配,无法将selectData.lpData指向的内存地址绑定给智能指针管理。尝试直接用C风格转换赋值均无法通过编译:
- 构造场景报错:无合适构造函数
- 赋值场景报错:=运算符无匹配操作数
错误写法示例:
// 构造时报错 std::shared_ptr<MEDIALAYOUTRANGE_4 []> mediaArr = (MEDIATYPERANGE_4 *)selectData.lpData; // 赋值时报错 mediaArr = (MEDIATYPERANGE_4 *)selectData.lpData;
正确实现方式
首先明确两个基础问题:
- 原始裸指针实现本身就存在内存泄漏:
new出来的数组指针马上被selectData.lpData覆盖,申请的内存既没有被使用也没有被释放。 - 不要直接把接口返回的
lpData交给智能指针默认析构:这块内存是驱动接口返回的,不是通过new申请的,默认用delete[]释放大概率触发程序崩溃。 - 你代码里的类型名拼写错误:
MEDIALAYOUTRANGE_4和实际结构体名MEDIATYPERANGE_4不一致,这也是编译报错的原因之一。
根据接口的内存管理规则,分两种场景实现:
场景1:lpData指向的内存由驱动自行管理,不需要应用释放
这种情况不需要额外申请内存,也不需要给返回的指针套智能指针,直接转换指针遍历访问即可,最后按驱动文档要求调用对应释放接口(如果有要求):
SELECTDATA selectData{}; GetSelectDataRange(CMD_MEDIATYPE, &selectData); // 直接转换接口返回的指针即可,不需要额外new内存 MEDIATYPERANGE_4* arr = reinterpret_cast<MEDIATYPERANGE_4*>(selectData.lpData); for (size_t i = 0; i < selectData.iCount; i++) { std::cout << arr[i].iMaxWid << '\n'; std::cout << arr[i].iMinWid << '\n'; std::cout << arr[i].iMaxHig << '\n'; std::cout << arr[i].iMinHig << '\n'; }
场景2:lpData需要应用自行分配内存,接口把数据写入应用分配的缓冲区
这才是结构体注释里描述的标准用法:注释明确说明应用必须为lpData成员分配缓冲区,你之前的调用流程写反了,正确流程是先获取需要的内存大小,分配缓冲区后把地址赋给lpData,再调用接口让驱动把数据写入你分配的内存。这种场景适合用智能指针管理自申请的内存:
SELECTDATA selectData{}; // 第一次调用传空lpData,获取需要的元素数量和单元素大小 GetSelectDataRange(CMD_MEDIATYPE, &selectData); // 校验单元素大小和结构体大小是否匹配,避免内存越界 if (selectData.iSize != sizeof(MEDIATYPERANGE_4) || selectData.iCount <= 0) { // 对应错误处理逻辑 return; } // 用智能指针管理自己分配的数组内存,出作用域自动调用delete[]释放 // 如果不需要共享所有权,更推荐用std::unique_ptr<MEDIATYPERANGE_4[]>,开销更小且C++14原生支持下标访问 std::shared_ptr<MEDIATYPERANGE_4[]> mediaArr(new MEDIATYPERANGE_4[selectData.iCount]()); // 把分配到的内存地址赋给lpData,供接口写入数据 selectData.lpData = reinterpret_cast<int*>(mediaArr.get()); // 第二次调用接口,让驱动把数据写入我们分配的缓冲区 GetSelectDataRange(CMD_MEDIATYPE, &selectData); // C++14下shared_ptr<T[]>没有实现operator[],需要通过get()拿到裸指针再访问下标 for (size_t i = 0; i < selectData.iCount; i++) { std::cout << mediaArr.get()[i].iMaxWid << '\n'; std::cout << mediaArr.get()[i].iMinWid << '\n'; std::cout << mediaArr.get()[i].iMaxHig << '\n'; std::cout << mediaArr.get()[i].iMinHig << '\n'; }
特殊场景:需要接管外部分配的内存
如果确实需要把驱动返回的内存交给shared_ptr管理,必须自定义和内存分配规则匹配的删除器,绝对不能用默认删除器。比如驱动要求用DriverFree接口释放传入的内存,写法如下:
// 自定义删除器,调用驱动提供的释放接口 auto driverDeleter = [](MEDIATYPERANGE_4* p) { // 调用驱动对应的内存释放接口,例如 DriverFree(p); }; std::shared_ptr<MEDIATYPERANGE_4[]> mediaArr( reinterpret_cast<MEDIATYPERANGE_4*>(selectData.lpData), driverDeleter );
内容的提问来源于stack exchange,提问作者rockstiff
相关产品推荐
相关产品推荐

