skip to content
代码与远方

C++ 技巧:使用 shared_ptr 别名构造函数实现“只引用、不管理”

/ 5 min read

0. 引言

在 C++ 开发中,我们经常面临一个尴尬的局面:某个库(如 PCL、OpenCV、TensorRT)的 API 强制要求传入 std::shared_ptr,但我们手里的对象却是从外部(如 C# 内存堆、硬件 DMA 缓冲区、或对象池)获取的原始指针(Raw Pointer)。

如果我们直接用 std::shared_ptr<T>(ptr),智能指针会在作用域结束时自动调用 delete,导致程序崩溃。本文介绍一种名为 别名构造函数(Aliasing Constructor) 的高级技巧,让你优雅地“骗过”API。


1. 核心代码

这种技巧的核心在于构造一个特殊的智能指针:它看起来像 shared_ptr,但内部不增加引用计数,也不负责释放内存。

C++

// 通用模板定义
using SharedPtr = std::shared_ptr<T>;
// 核心技巧:构造一个“伪”共享指针
SharedPtr fake_ptr = SharedPtr(SharedPtr(), raw_ptr);

2. 原理解析

std::shared_ptr 提供了一个特殊的构造函数重载:

C++

template< class Y >
shared_ptr( const shared_ptr<Y>& r, element_type* ptr ) noexcept;
  • 参数 r (控制块):新创建的指针将共享 r 的引用计数器。
  • 参数 ptr (数据地址):新创建的指针实际指向的内存地址。

当我们传入一个空的 SharedPtr() 作为第一个参数时,新指针就没有有效的控制块。这意味着:

  1. 引用计数失效:它不参与所有权管理。
  2. 析构安全:当 fake_ptr 析构时,由于没有控制块,它不会去调用 delete 释放 raw_ptr

3. 三大典型应用场景

A. 跨语言内存共享(C# 调用 C++)

在 P/Invoke 开发中,内存通常由 C# 侧管理。在 C++ 侧调用 PCL 等库时,必须保证 C++ 不会误杀内存。

C++

// PCL 圆柱拟合示例
void FitCylinder(pcl::PointCloud<pcl::PointXYZ>* cloud) {
auto cloud_ptr = std::shared_ptr<pcl::PointCloud<pcl::PointXYZ>>(
std::shared_ptr<pcl::PointCloud<pcl::PointXYZ>>(),
cloud
);
seg.setInputCloud(cloud_ptr); // 安全调用,cloud 不会被释放
}

B. 硬件缓冲区与 AI 推理

在深度学习推理中,输入 Tensor 通常位于硬件固定的 DMA 区域或显存中,由驱动层负责管理。

C++

// 假设引擎要求 shared_ptr
void SetInput(std::shared_ptr<Buffer> buf);
void OnFrame(Buffer* hardware_buf) {
// 包装但不拥有
auto wrapper = std::shared_ptr<Buffer>(std::shared_ptr<Buffer>(), hardware_buf);
inference_engine->SetInput(wrapper);
}

C. 指向成员变量(原始意图)

这其实是别名构造函数的“正统”用法:让智能指针指向对象的某个成员,同时保证整个对象不被销毁。

C++

struct Robot { Arm arm; };
std::shared_ptr<Robot> my_robot = std::make_shared<Robot>();
// 指向 arm 成员,但共享 my_robot 的控制块
// 只要 arm_ptr 还在,整个 robot 就不会被销毁
std::shared_ptr<Arm> arm_ptr = std::shared_ptr<Arm>(my_robot, &my_robot->arm);

4. 方案对比

方法代码内存权属优点缺点
标准构造shared_ptr<T>(raw)C++ 接管简单会误删外部内存
别名构造shared_ptr<T>(empty, raw)外部保留零开销、接口兼容需自行保证内存有效期
空删除器shared_ptr<T>(raw, [](T*){})外部保留语义清晰需分配控制块空间,开销略大

5. 总结与注意事项

“别名构造” 是处理 C++ 遗留 API 或跨系统交互时的有力武器。但请务必记住:

你必须保证原始指针在所有智能指针副本销毁之前,始终是有效的。

如果外部内存提前释放,C++ 侧的“伪”智能指针将变成悬空指针,访问它会导致未定义行为。