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

自定义SharedPtr实现是否符合标准规定的线程安全性?

自定义SharedPtr的线程安全性是否符合C++标准?

我了解std::shared_ptr具备一定的线程安全性,核心保证是:当多个shared_ptr实例同时销毁时,指向的对象只会被销毁一次。而智能指针的其他操作(如拷贝、移动等)并不具备线程安全性,这个设计是合理的——毕竟没有显式互斥锁的话,很难实现这类线程安全。

现在问题是:下面的SharedPtr实现是否符合C++标准规定的线程安全性要求?

#include <atomic>
#include <utility>

template<class T> struct SharedPtr {
private:
  std::atomic<int> *pRefCnt_ = nullptr;
  T *pObj_ = nullptr;

  void destroy() {
    if (pRefCnt_ && *pRefCnt_-- == 0) {
      delete std::exchange(pRefCnt_, nullptr);
      delete std::exchange(pObj_, nullptr);
    }
  }

  void copy(SharedPtr const &other) {
    pRefCnt_ = other.pRefCnt_;
    pObj_ = other.pObj_;
    if (pRefCnt_) {
      ++*pRefCnt_;
    }
  }

  void move(SharedPtr &&other) {
    pRefCnt_ = std::exchange(other.pRefCnt_, nullptr);
    pObj_ = std::exchange(other.pObj_, nullptr);
  }

public:
  SharedPtr() = default;

  explicit SharedPtr(T *p) : pRefCnt_(new std::atomic<int>(0)), pObj_(p) {}

  SharedPtr(SharedPtr const &rhs) noexcept { copy(rhs); }

  SharedPtr(SharedPtr &&rhs) noexcept { move(std::move(rhs)); }

  SharedPtr &operator=(SharedPtr const &rhs) noexcept {
    if (this != &rhs) {
      destroy();
      copy(rhs);
    }
    return *this;
  }

  SharedPtr &operator=(SharedPtr &&rhs) noexcept {
    if (this != &rhs) {
      destroy();
      move(std::move(rhs));
    }
    return *this;
  }

  ~SharedPtr() { destroy(); }

  T *operator*() const { return pObj_; }
  T &operator->() const { return *pObj_; }
  explicit operator bool() const { return pObj_ != nullptr; }
};

核心问题分析

这个自定义SharedPtr的实现不符合C++标准对shared_ptr线程安全性的要求,主要存在以下几个关键问题:

  • 引用计数递减的原子性问题
    destroy()函数中的*pRefCnt_-- == 0是致命错误:这行代码会先读取原子变量的当前值,再执行递减操作,两个步骤并非原子的。当多线程同时销毁指向同一对象的SharedPtr时,可能出现多个线程都读取到引用计数为1,随后都执行递减,最终导致引用计数变为负数,且多个线程都会进入销毁分支,重复释放对象和引用计数指针,触发未定义行为。
    正确的做法是使用std::atomic::fetch_sub原子操作,确保判断和递减的原子性:

    if (pRefCnt_ && pRefCnt_->fetch_sub(1) == 1) {
        delete std::exchange(pRefCnt_, nullptr);
        delete std::exchange(pObj_, nullptr);
    }
    
  • 构造函数引用计数初始化错误
    使用原始指针构造SharedPtr时,引用计数被初始化为0,但正确的初始值应该是1——当前SharedPtr已经持有对象的引用,计数从1开始才符合逻辑。这个错误会导致第一个销毁的SharedPtr就触发对象销毁,此时若还有其他SharedPtr持有该对象,会直接产生悬空指针。

  • 拷贝操作的竞态条件
    copy()函数中先赋值pRefCnt_ = other.pRefCnt_,再执行++*pRefCnt_。如果另一个线程刚好在赋值完成后、递增操作前销毁了other对应的SharedPtr并释放了pRefCnt_,当前线程对*pRefCnt_的递增就会访问已释放的内存,引发未定义行为。正确逻辑应该是先对other的引用计数做原子递增,再赋值给自己的指针,确保计数递增完成前,引用计数不会被释放。

总结

该实现存在多处线程安全漏洞,无法满足C++标准中shared_ptr的核心线程安全保证——多线程销毁时对象仅被销毁一次。要符合标准,必须保证引用计数的增减都是原子复合操作,同时修正初始化和拷贝逻辑中的竞态问题。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.24 21:27:11