实现C++自定义shared_ptr时如何区分数组指针与普通指针
由于new T和new T[N]返回的指针类型完全一致,编译期无法直接通过类型区分指针来源,所以需要从调用入口做限制。以下两种解决方案都可以在C11及以上版本实现,不需要C17支持:
方案1:工厂函数隔离(最稳妥,推荐)
完全禁止用户直接传递裸指针构造智能指针,通过工厂函数从内部保证指针类型和删除器匹配:
- 将所有
myptr接受裸指针的构造函数设为私有,仅允许工厂函数访问 - 分别实现非数组和数组版本的工厂函数:
// 非数组版本工厂函数 template <typename T, typename... Args> myptr<T> make_myptr(Args&&... args) { return myptr<T>(new T(std::forward<Args>(args)...)); } // 数组版本工厂函数 template <typename T> myptr<T[]> make_myptr_array(size_t n) { return myptr<T[]>(new T[n]); }
- 使用时只能通过工厂函数构造,完全避免传错:
// 正确用法 auto arr_ptr = make_myptr_array<char>(10); // 内部自动调用new char[10],匹配delete[]删除器 auto obj_ptr = make_myptr<char>('a'); // 内部自动调用new char,匹配delete删除器 // 错误用法根本无法写出,从调用层面杜绝未定义行为 // 即使写make_myptr_array<char>(1)也没问题,内部是new char[1],用delete[]删除完全合法
方案2:标签分派(保留裸指针构造能力)
如果业务需要保留用户直接传裸指针的能力,可以通过标签强制用户显式标注指针类型,避免误传:
- 定义空标签类型用于标记数组指针:
struct array_pointer_tag {}; constexpr array_pointer_tag array_ptr_tag{};
- 修改数组特化版本的构造函数,删除无标签的裸指针构造接口,仅保留带标签的构造函数:
template <typename T, typename DeleterType> class myptr<T[], DeleterType> { private: my_ptr_cnt<T, DeleterType> *p; public: // 删除无标签的裸指针构造接口,避免误调用 myptr(T* p) = delete; // 仅允许带标签的构造,附加的SFINAE约束和C++17标准库逻辑一致,避免派生类数组转基类数组的未定义行为 template <typename Y, typename = typename std::enable_if<std::is_convertible<Y(*)[], T(*)[]>::value>::type> myptr(Y* p, array_pointer_tag, DeleterType deleter=[](T* p) {delete[] p;}) : p(new my_ptr_cnt<T, DeleterType>(static_cast<T*>(p), deleter)) { } };
- 使用时必须显式传入标签,用户自然会确认传入的是数组指针:
// 正确用法 myptr<char[]> arr_ptr(new char[10], array_ptr_tag);
内容的提问来源于stack exchange,提问作者陈泽霖
相关产品推荐
相关产品推荐

