C++自定义智能指针模板类转型后虚函数访问冲突问题求解
问题解答
1. 更合适的现成方案
完全不需要自行实现智能指针,用C++标准库组件即可组合出满足你需求的能力:
你需要的核心特性是「库侧可主动作废所有用户持有的指针副本、引用计数自动释放、支持多态转型」,使用std::shared_ptr<std::unique_ptr<T>>即可实现:
- 库内部持有主控的
std::shared_ptr<std::unique_ptr<接口类>>实例 - 给用户返回该
shared_ptr的拷贝 - 需要主动释放对象时,直接调用
主控指针->reset(),所有用户持有的副本里的unique_ptr都会同步变为空,用户通过if (*用户指针)即可判断有效性 - 多态转型直接使用标准库提供的
std::static_pointer_cast、std::dynamic_pointer_cast即可,所有边界情况标准库都已处理,同时原生支持线程安全、异常安全,可靠性远高于自行实现的版本。
2. 现有实现的合理性问题
你的设计思路是对的,但代码存在几个明显缺陷:
- 引用计数不是原子类型,多线程场景下会出现竞争,导致double free、内存泄漏等问题
- 拷贝赋值运算符没有处理自赋值场景:自赋值时会先减少引用计数把当前
pw释放,后续访问野指针直接触发崩溃 - 用
reinterpret_cast强制转换不同类型的pointer_wrapper指针属于完全的未定义行为:多态类父子类指针转型往往需要地址偏移,直接转包装类指针会导致内部存储的对象指针没有做对应偏移,调用虚函数时访问错误的虚表地址,就是你遇到的执行权限违例的根本原因 - 没有实现移动构造、移动赋值,会产生不必要的引用计数增减操作,性能有损耗
- 如果接口基类没有定义虚析构函数,
destroy方法删除基类指针时会产生未定义行为。
3. 正确的convert实现方法
核心改造思路是把引用计数和类型解耦,抽出非模板的基类存储引用计数,模板子类只负责类型相关的对象操作:
第一步:定义无类型的引用计数基类
#include <atomic> #include <type_traits> #include <stdexcept> class pointer_wrapper_base { private: std::atomic<int32_t> reference_count = 1; public: void increase_reference_count() { reference_count.fetch_add(1, std::memory_order_relaxed); } void decrease_reference_count() { if(reference_count.fetch_sub(1, std::memory_order_acq_rel) == 1) { delete this; } } int32_t get_reference_count() const { return reference_count.load(std::memory_order_acquire); } virtual void destroy() = 0; virtual bool is_valid() const = 0; virtual ~pointer_wrapper_base() = default; };
第二步:修改模板化的包装类继承基类
template<class T> class pointer_wrapper : public pointer_wrapper_base { private: T* raw_pointer; public: explicit pointer_wrapper(T* const raw_pointer): raw_pointer(raw_pointer) { } T* get_raw_pointer() const { return raw_pointer; } void destroy() override { if(raw_pointer != nullptr) { delete raw_pointer; raw_pointer = nullptr; } } bool is_valid() const override { return raw_pointer != nullptr; } ~pointer_wrapper() override { destroy(); } };
第三步:修改ptr类实现安全的convert方法
template<class T> class ptr { private: pointer_wrapper_base* pw_base = nullptr; T* cached_raw = nullptr; // 缓存转型后的指针,避免重复转换 // 内部构造方法,供convert调用 template<class X> ptr(pointer_wrapper_base* base, X* raw) : pw_base(base), cached_raw(static_cast<T*>(raw)) { if(pw_base) pw_base->increase_reference_count(); } public: explicit ptr(T* const raw_pointer) { if(raw_pointer) { pw_base = new pointer_wrapper<T>(raw_pointer); cached_raw = raw_pointer; } } ptr(const ptr<T>& other) : pw_base(other.pw_base), cached_raw(other.cached_raw) { if(pw_base) pw_base->increase_reference_count(); } ptr<T>& operator=(const ptr<T>& other) { if(this == &other) return *this; // 处理自赋值 if(pw_base) pw_base->decrease_reference_count(); pw_base = other.pw_base; cached_raw = other.cached_raw; if(pw_base) pw_base->increase_reference_count(); return *this; } T* operator->() const { if(!is_valid()) throw std::runtime_error("accessing invalid pointer"); return cached_raw; } bool is_valid() const { return pw_base != nullptr && pw_base->is_valid() && cached_raw != nullptr; } int32_t get_reference_count() const { return pw_base ? pw_base->get_reference_count() : 0; } void destroy() { if(pw_base) pw_base->destroy(); } // 向上转型(编译期安全) template<class X, std::enable_if_t<std::is_base_of_v<X, T>, int> = 0> ptr<X> convert() const { if(!is_valid()) return ptr<X>(nullptr, nullptr); return ptr<X>(pw_base, static_cast<X*>(cached_raw)); } // 向下转型(运行期检查,转型失败返回空指针) template<class X, std::enable_if_t<std::is_base_of_v<T, X>, int> = 0> ptr<X> convert() const { if(!is_valid()) return ptr<X>(nullptr, nullptr); X* casted = dynamic_cast<X*>(cached_raw); return ptr<X>(pw_base, casted); } ~ptr() { if(pw_base) pw_base->decrease_reference_count(); } };
改造后所有同对象的不同类型ptr实例共享同一个引用计数和销毁标记,调用任意一个实例的destroy方法都会让所有实例的is_valid返回false,同时转型逻辑符合C++标准要求,不会出现未定义行为。
内容的提问来源于stack exchange,提问作者racz16
相关产品推荐
相关产品推荐

