You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

C++中不调用默认构造函数创建初始化cuda_ptr类数组的方法

问题本质
  • C++原生固定长度数组如果声明时不做显式聚合初始化,会强制要求元素类型提供可访问的默认构造函数,这是之前不得不保留无参默认构造的核心原因。
  • 观察到的临时对象析构现象判断是正确的:循环赋值时生成的临时cuda_ptr对象,在赋值操作完成后会立刻析构。由于当前类没有自定义拷贝/移动赋值逻辑,默认生成的赋值操作只会浅拷贝data指针,临时对象析构时会直接调用cudaFree释放指针,最终数组元素存储的是已经被释放的野指针,会触发double free或者非法访问错误。
实现方案

首先先对cuda_ptr类做必要的RAII合规修改,从根源上禁止非法操作:

  1. 直接删除默认构造函数,不允许无参创建无效的cuda_ptr实例
  2. 删除拷贝构造、拷贝赋值函数,避免同一块显存被多个cuda_ptr对象持有导致重复释放
  3. 实现移动构造、移动赋值函数,支持显存所有权的安全转移

修改后的类代码如下:

template <typename T>
class cuda_ptr
{
public:
    // 禁用默认构造
    cuda_ptr() = delete;
    cuda_ptr(size_t device_number, T* allocdata )
        : dev_number(device_number), data(allocdata)
    {
        std::cout<<"create pointer"<<std::endl;
        safeCudaSelectDevice(dev_number);
    }
    // 禁用拷贝
    cuda_ptr(const cuda_ptr&) = delete;
    cuda_ptr& operator=(const cuda_ptr&) = delete;
    // 实现移动语义
    cuda_ptr(cuda_ptr&& other) noexcept
        : dev_number(other.dev_number), data(other.data)
    {
        other.data = nullptr;
        other.dev_number = 0;
    }
    cuda_ptr& operator=(cuda_ptr&& other) noexcept
    {
        if (this != &other) {
            // 释放当前持有的显存
            if (data != nullptr) {
                safeCudaSelectDevice(dev_number);
                cudaFree(data);
            }
            // 接管对方的显存所有权
            dev_number = other.dev_number;
            data = other.data;
            other.data = nullptr;
            other.dev_number = 0;
        }
        return *this;
    }
    ~cuda_ptr(){
        if (data != nullptr) {
            safeCudaSelectDevice(dev_number);
            cudaFree(data);
            std::cout<<"delete pointer"<<std::endl;
        }
    }
    
    size_t dev_number;
    T* data;
};

固定长度数组最优方案:std::array配合编译期初始化

std::array和原生数组内存布局完全一致,没有额外性能开销,配合编译期索引序列可以直接对所有元素做显式构造,全程不触发默认构造,也没有临时对象析构问题。

先写两个辅助模板函数,自动按设备序号构造数组元素:

template <typename T, size_t... Is>
std::array<cuda_ptr<T>, sizeof...(Is)> make_cuda_ptr_array_impl(size_t elem_bytes, std::index_sequence<Is...>) {
    return std::array<cuda_ptr<T>, sizeof...(Is)>{
        cuda_ptr<T>(Is, safeCudaMalloc<T>(elem_bytes))...
    };
}

template <size_t ArrayLen, typename T>
std::array<cuda_ptr<T>, ArrayLen> make_cuda_ptr_array(size_t elem_count) {
    return make_cuda_ptr_array_impl<T>(elem_count * sizeof(T), std::make_index_sequence<ArrayLen>{});
}

使用方式非常简单,直接声明即可:

// 创建长度为2的cuda_ptr<float>数组,两个元素分别绑定设备0、1,各分配N个float的显存
auto dev_A = make_cuda_ptr_array<2, float>(N);
// 访问方式和原生数组完全一致:dev_A[0].data 就是设备0上的显存指针

可变长度数组方案:std::vector配合原位构造

如果数组长度需要运行时确定,使用std::vector提前预留空间后调用emplace_back原位构造元素,同样不会触发默认构造:

std::vector<cuda_ptr<T>> dev_A;
// 提前预留足够空间,避免扩容时的移动开销
dev_A.reserve(array_length);
for(int i = 0; i < array_length; i++){
    // 直接在vector的内存位置构造元素,不生成临时对象
    dev_A.emplace_back(i, safeCudaMalloc<T>(N * sizeof(T)));
}

注意:不要使用原生数组+placement new的手动内存管理方案,很容易遗漏构造、析构调用,触发难以排查的内存错误,标准库容器已经对这类场景做了完善的封装,没有必要重复实现。

内容的提问来源于stack exchange,提问作者JanJHF

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.28 13:27:22