在 C++ 中使用智能指针时需要考虑指针指向资源的回收吗?如果是,如何回收。如果不是,系统如何自动回收。另外智能指针可以指向设备显存吗?为什么?

简短的回答是:

  1. 普通内存不需要手动回收,系统通过 RAII(资源获取即初始化)机制自动回收。
  2. 设备显存(Device Memory):智能指针可以指向显存,但必须配置自定义删除器(Custom Deleter),否则会导致程序崩溃。

下面详细拆解这背后的原理和实现方法。


第一部分:普通内存的自动回收(CPU 端)

如果你使用 std::shared_ptrstd::unique_ptr 管理通过 new 分配的普通 CPU 内存,你完全不需要考虑回收。

1. 系统如何自动回收?

C++ 智能指针的核心机制是 RAII(Resource Acquisition Is Initialization)

代码对比:

void oldWay() {
    int* data = new int[100];
    // ... 如果这里抛出异常,delete 将不会执行,导致内存泄漏
    delete[] data; // 必须手动回收
}

void smartWay() {
    // 离开函数作用域时,p 的析构函数自动被调用,内存自动释放
    std::unique_ptr<int[]> p = std::make_unique<int[]>(100); 
    // 不需要写 delete
}

第二部分:智能指针指向设备显存(GPU 端)

1. 智能指针可以指向显存吗?

可以。

2. 为什么可以?

从 C++ 语言层面看,指针只是一个存储地址的整数
无论这个地址是 0x7ff... (CPU RAM) 还是 0x200... (GPU VRAM),对于智能指针对象本身(它位于 CPU 栈上)来说,它只负责保存这个地址值。

3. 核心陷阱:回收方式不同

这是最关键的一点。

如果你直接把 cudaMalloc 分配的指针交给默认的 std::unique_ptr,它在析构时会尝试对显存地址调用 CPU 的 delete,这会导致 Segmentation Fault (段错误)未定义行为

4. 解决方案:自定义删除器 (Custom Deleter)

你必须告诉智能指针:“嘿,这块内存不用 delete 删,请用 cudaFree 删。”

实现代码示例:

我们可以定义一个非常轻量级的结构体来实现这个删除逻辑。

#include <memory>
#include <cuda_runtime.h>
#include <iostream>

// 1. 定义一个仿函数(Functor)作为删除器
struct CudaDeleter {
    void operator()(void* ptr) {
        if (ptr) {
            cudaFree(ptr); // 使用 CUDA 专用的释放函数
            std::cout << "GPU Memory freed automatically!" << std::endl;
        }
    }
};

// 2. 定义一个别名,方便使用 (推荐做法)
// 这是一个指向 float,且使用 CudaDeleter 的 unique_ptr
using CudaUniquePtr = std::unique_ptr<float, CudaDeleter>;

int main() {
    float* raw_ptr = nullptr;
    size_t size = 1024 * sizeof(float);

    // 分配显存
    cudaMalloc(&raw_ptr, size);

    {
        // 接管显存所有权
        CudaUniquePtr dev_ptr(raw_ptr);

        // ... 在这里使用 dev_ptr.get() 进行 Kernel 运算 ...
        // kernel<<<...>>>(dev_ptr.get(), ...);

    } // <--- 出了这个大括号,dev_ptr 析构,自动调用 CudaDeleter::operator(),执行 cudaFree

    // 此时显存已被安全回收
    return 0;
}

进阶优化:封装成工厂函数

为了避免每次都手动写 cudaMalloc,可以模仿 std::make_unique 封装一个分配器:

template <typename T>
std::unique_ptr<T, CudaDeleter> make_device_unique(size_t n_elements) {
    T* raw_ptr = nullptr;
    // 注意:这里没有处理 cudaMalloc 可能的错误,实际工程中需要检查
    cudaMalloc(&raw_ptr, n_elements * sizeof(T));
    return std::unique_ptr<T, CudaDeleter>(raw_ptr);
}

// 使用方式极其清爽:
void your_simulation_function() {
    auto u_dev = make_device_unique<float>(1000); // 自动分配
    auto v_dev = make_device_unique<float>(1000); 
    
    // 传给 Kernel 时使用 .get()
    // kernel<<<...>>>(u_dev.get(), v_dev.get());
    
    // 函数结束,自动 cudaFree
}

总结