模板类初始化带自定义删除器的N个std::unique_ptr构造报错求助
问题原因
- 声明顺序错误:
ptr_type类型别名定义在类的public区域,位置晚于私有构造函数的实现,构造函数中使用ptr_type时编译器尚未识别该类型,触发构造函数匹配失败错误。 - 版本兼容问题:若使用C++20之前的标准,无捕获lambda类型不支持默认构造,部分编译器在批量初始化
std::unique_ptr数组时,即使显式传入deleter也可能触发构造校验失败。
修复方案
- 调整
Storage类的成员声明顺序,将公共类型别名移到类的最开头,保证构造函数使用ptr_type时该类型已被定义。 - 替换lambda deleter为显式定义的仿函数,兼容所有C++11及以上标准,避免lambda默认构造的兼容问题。
- 修正数组初始化的语法,保证折叠展开的初始化列表符合聚合初始化要求。
修正后完整代码
#include <memory> #include <utility> #include <sys/mman.h> #include <unistd.h> // 单独定义deleter仿函数,替代lambda,兼容所有C++版本 template<typename Tp> struct PeripheralDeleter { void operator()(Tp* ptr) const { if (ptr) munmap(ptr, sizeof(Tp)); } }; template<typename Tp, std::size_t N> class Storage { public: // 公共类型定义移到最前面 using ptr_type = std::unique_ptr<Tp, PeripheralDeleter<Tp>>; ptr_type ptr[N]; Storage() : Storage(std::make_index_sequence<N>{}) {} private: template<size_t ... indices> explicit Storage(std::index_sequence<indices...> const&) // 修正初始化语法,直接构造每个unique_ptr实例 : ptr{ptr_type(nullptr, PeripheralDeleter<Tp>{})...} { // 消除未使用参数的编译警告 (void)std::initializer_list<size_t>{indices...}; } }; template<typename Tp, size_t nControllers = 1, size_t offset = 0> static Tp* getPeripheralPtr(unsigned base_address, int idx = 0, int mem_fd = -1) { // 边界校验,避免数组越界 if (idx < 0 || idx >= nControllers) return nullptr; static Storage<Tp, nControllers> storage; if (!storage.ptr[idx]) { // 计算当前控制器的物理地址 unsigned phy_addr = base_address + idx * offset; long page_size = sysconf(_SC_PAGESIZE); // 按页对齐计算mmap参数 off_t mmap_offset = phy_addr & ~(page_size - 1); size_t mmap_len = (phy_addr + sizeof(Tp) + page_size -1) & ~(page_size -1); // 此处mem_fd为/dev/mem的打开文件描述符,需提前以读写权限打开,进程需要root权限 void* virtaddr = mmap(nullptr, mmap_len, PROT_READ | PROT_WRITE, MAP_SHARED, mem_fd, mmap_offset); if (virtaddr == MAP_FAILED) return nullptr; // 调整到寄存器实际的虚拟地址 Tp* reg_addr = reinterpret_cast<Tp*>(reinterpret_cast<uintptr_t>(virtaddr) + (phy_addr & (page_size - 1))); storage.ptr[idx].reset(reg_addr); } return storage.ptr[idx].get(); }
注意事项
- 调用
getPeripheralPtr时,offset参数要传入实际的两个控制器物理地址的偏移量,比如PWM0基地址是0x10000,PWM1是0x10100,offset就传0x100。 - mmap操作需要提前打开
/dev/mem设备获取文件描述符,且进程需要root权限,否则会映射失败。
内容的提问来源于stack exchange,提问作者Quest
相关产品推荐
相关产品推荐

