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

为何std::shared_ptr别名构造函数不采用传值语义?

std::shared_ptr别名构造函数的传值语义与右值重载问题

一、为什么最初用const引用而非传值语义?

std::shared_ptr的别名构造函数(第8项)最初设计为接收const shared_ptr<_Yp>&而非传值,核心原因是避免不必要的拷贝开销与语义歧义:

  • 传值语义会强制触发参数的拷贝/移动构造,对于左值输入,拷贝构造会先无条件递增引用计数,而原const引用版本仅做指针复制,再在构造函数内按需递增计数,避免了shared_ptr对象本身的拷贝开销。
  • 早期标准无右值引用支持,const引用是当时兼顾左值输入、避免冗余拷贝的最优选择,同时能明确表达“共享原对象控制块”的语义。

参考libcxx的实现:

shared_ptr(const shared_ptr<_Yp>& __r, element_type *__p) _NOEXCEPT
    : __ptr_(__p),
      __cntrl_(__r.__cntrl_)
{
    if (__cntrl_)
        __cntrl_->__add_shared();
}

它直接复用原shared_ptr的控制块指针,仅做必要的指针复制和计数操作,没有多余的对象拷贝。

二、能否改用传值实现依赖拷贝构造递增计数?

你给出的传值版本存在严重逻辑错误,不能替代原实现:

template<class _Yp>
shared_ptr(shared_ptr<_Yp> __r, element_type *__p) _NOEXCEPT
    : __ptr_(__p),
      __cntrl_(__r.__cntrl_) {}

当传入右值时,__r通过移动构造得到:移动操作会把源shared_ptr的控制块转移到__r,但不会递增引用计数。构造完成后__r被销毁,会触发控制块的引用计数递减——这会导致新创建的shared_ptr所持有的控制块计数被错误减少,极端情况下会提前释放控制块,引发悬空指针问题。

即便针对左值输入,传值版本的拷贝构造会递增计数,最终效果和原实现一致,但这是用额外的shared_ptr对象拷贝换来了“省去构造函数内的计数递增”,本质上是得不偿失的性能损耗。

三、C++20为何新增右值重载而非改用传值单重载?

C++20新增右值引用版本的别名构造函数,而非改用传值单重载,是兼容性、性能精准优化与语义清晰性三者平衡的结果:

  1. 兼容性:原const引用版本已被广泛使用,改用传值版本会破坏现有代码的行为(比如上述右值输入的逻辑错误),同时传值版本的模板匹配规则可能和现有构造函数产生冲突,引发兼容性问题。
  2. 性能精准优化:右值重载可针对右值输入做零开销优化——当传入右值shared_ptr时,直接接管其控制块,不需要递增引用计数(因为右值本身即将被销毁,转移控制块后,原右值的销毁不会减少计数)。典型实现类似:
    shared_ptr(shared_ptr<_Yp>&& __r, element_type *__p) _NOEXCEPT
        : __ptr_(__p),
          __cntrl_(__r.__cntrl_)
    {
        __r.__cntrl_ = nullptr; // 转移控制块所有权,避免__r销毁时递减计数
    }
    
    这种实现完全避免了原子操作开销,是传值版本无法做到的精准优化。
  3. 语义清晰性:分开左值和右值重载,能明确表达不同输入的语义:左值输入意味着共享原对象控制块,需要递增计数;右值输入意味着转移原对象控制块使用权,不需要递增计数。传值版本会模糊这种语义,让开发者难以理解背后的计数逻辑。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.24 15:57:19