1. 为什么要传入“二级指针” (**)?
当你需要 c++ 内部产生一份未知大小的数据,并把数据交还给外部时:
- 如果你传一级指针 (
datapoints* ptr):c++ 内部执行ptr = new datapoints[10];时,修改的只是ptr这个变量在栈上的局部副本。函数一结束,外部的指针依然是nullptr,不仅拿不到数据,还会造成内存泄漏。 - 传入二级指针 (
datapoints** ptr_addr):你传进来的是“外部指针变量的地址”。c++ 内部执行*ptr_addr = new datapoints[10];时,是直接顺着地址找到了外部的那个指针,把新分配的内存首地址硬塞给它。这样外部就能成功拿到数据了。
2. 必须“c++ 内部分配,并提供内部接口释放”
传入一级指针通常用于以下 三大黄金场景:
场景一:只读的数据输入(input arrays / structs)
当需要把大量数据从 c# 传给 c++ 让它进行计算时,绝不会把几十万个坐标点按值(by value)传进去,而是传首地址(一级指针)。
- 工作流:c# 在自己的托管堆(或非托管堆)上准备好了一排
datapoints,然后把**首个元素的地址(一级指针)**传给 c++。c++ 内部只做遍历和读取(bins[i].datapoints_x),绝对不会对bins执行new或delete。 - 总结:用于**“只读”**的大块数据传输。
场景二:调用方预分配内存的高速填充
这是工业视觉和音视频处理中最高效、最极客的输出模式。 如果 c# 端提前知道计算结果大概有多大(或者结果大小是固定的),那么由 c# 提前申请好内存,传一级指针给 c++ 去“填空”,是比“二级指针内部 new”更快的做法!
举个实际的例子: 假设你的点云重采样后固定是 1200 个点,c# 完全可以自己提前 new double[1200]。
c++ 接口设计:
// 传入一级指针 pre_alloc_x 和 pre_alloc_y
void processsinglecloud(double* pre_alloc_x, double* pre_alloc_y, int fixed_len) {
// c++ 内部绝对不写 new!直接往外部传进来的地址里塞数据
for(int i=0; i<fixed_len; i++) {
pre_alloc_x[i] = ...; // 直接填充
pre_alloc_y[i] = ...;
}
}c# 调用方:
// c# 自己分配好内存 double[] out_x = new double[1200]; double[] out_y = new double[1200]; // 传首地址(一级指针)给 c++ processsinglecloud(out_x, out_y, 1200); // 调用结束,数据已经在 out_x 里了,完全不需要管释放问题(c# 的 gc 会自动回收)!
优势:彻底干掉了 freedatapoints 这步操作!没有任何跨语言释放内存的风险,性能达到绝对的物理极限。
劣势:如果 c++ 计算出来的结果大小是未知的(比如不确定会返回 500 个点还是 800 个点),c# 就无法提前精准分配内存,这时候就只能退回“二级指针内部 new”的方案了。
3 vector结合二级指针
既然 vector 这么好用(比如不确定最终会匹配出多少个结果时,可以随时 push_back),我们当然要在内部用它。
正确的架构模式是:数据在函数内部完全用 std::vector 装载,但在函数的最后一行,把 vector 里的数据“过继(copy/move)”给一个通过 new[] 分配的裸数组。
完美结合 vector 的代码实现:
void f_findsimilarxldpoint(..., datapoints** datapoints_tf, int* datapoints_tfcount) {
// 1. 内部愉快地使用 vector,享受动态扩容的便利
std::vector<datapoints> temp_results;
for (int i = 0; i < batch; ++i) {
// 假设某些条件不满足,直接 continue,最终数量不确定
if (/* 匹配失败 */ false) continue;
// 构造单个结果
datapoints dp;
dp.datapoints_lenth = 1200;
// 🌟 注意:底层坐标数组必须也是 new 出来的,因为要传给外部
dp.datapoints_x = new double[1200];
dp.datapoints_y = new double[1200];
// ... 填充坐标数据 ...
temp_results.push_back(dp); // 装入 vector
}
// ==========================================================
// 2. 🌟 核心交接仪式 (transfer ownership)
// ==========================================================
int final_count = temp_results.size();
*datapoints_tfcount = final_count;
if (final_count > 0) {
// 分配一块干净的裸数组内存
datapoints* out_array = new datapoints[final_count];
// 浅拷贝:把 vector 里的 datapoints 结构体(包含里面的 x, y 指针)
// 逐个复制给 out_array
for (int i = 0; i < final_count; ++i) {
out_array[i] = temp_results[i];
}
// 把裸数组的地址交给二级指针
*datapoints_tf = out_array;
} else {
*datapoints_tf = nullptr;
}
} // <--- 函数结束,temp_results(vector) 被销毁。
// 但是不用担心!因为 vector 里装的是指针副本,
// 真正的数据 (new double[] 和 new datapoints[]) 已经挂在 out_array 上活下来了!极小开销: 你可能会担心最后的 for 循环复制会慢。其实完全不会!这里发生的是浅拷贝 (shallow copy),仅仅是复制了 datapoints 结构体里的 3 个变量(两个指针,一个 int),并没有复制那 1200 个 double 数据。就算有 1000 个零件,复制 1000 个结构体的时间连 0.01 毫秒都不到。
到此这篇关于c++接口内部内存分配问题设计方案的文章就介绍到这了,更多相关c++接口内部内存分配内容请搜索代码网以前的文章或继续浏览下面的相关文章希望大家以后多多支持代码网!
发表评论