浏览知识库目录

C++

手写 std::shared_ptr

从控制块、原子强引用计数和 make_shared 同址分配出发,实现复制共享、别名构造与确定性销毁。

手写 std::shared_ptr

从控制块、原子强引用计数和 make_shared 同址分配出发,实现复制共享、别名构造与确定性销毁。

本系列代码使用 C++20 和 oc::handmade 命名空间,目标是解释实现机制、复杂度和工程边界,不是替代标准库。智能指针管理生命周期,但不会自动解决业务对象的线程安全。


一、学习目标

  • 把对象指针与共享控制块的生命周期分开
  • 实现复制共享、移动、reset、use_count 和别名构造
  • 理解 make_shared 的同址分配与原子强计数边界

二、前置条件

完成 unique_ptr 篇,熟悉原子操作、虚函数类型擦除、placement new、对齐和异常安全。

Linux/macOS:

g++ -std=c++20 -Wall -Wextra -Wpedantic -Werror smart_ptrs_test.cpp -o smart_ptrs_test
./smart_ptrs_test

Windows PowerShell:

cl /std:c++20 /W4 /WX /EHsc smart_ptrs_test.cpp
.\smart_ptrs_test.exe

三、问题与设计选择

每组共享所有者指向同一控制块。控制块保存原子强计数、原子弱计数和销毁策略;原始指针构造使用指针控制块,make_shared 使用内嵌对象控制块,让对象与计数只分配一次。shared_ptr 另存一个可别名的观察指针。

教学实现覆盖核心所有权语义、复制或移动、观察接口、异常回滚与主要辅助函数;不复刻标准库的完整转换构造矩阵、allocator 定制、enable_shared_from_this、owner 比较器和 ABI 细节。


四、内存布局与核心不变量

强计数等于仍拥有对象的 shared_ptr 数量;强计数从 1 变为 0 的线程恰好销毁一次对象。控制块还有一个代表“强计数非零”的隐式弱引用,因此对象销毁后控制块仍可供 weak_ptr 查询。

判断实现是否正确时,不只数对象指针,还要分别追踪“资源是否活着”“拥有者数量”和“控制块是否活着”。三者的生命周期边界并不相同。


五、核心实现

void release_strong() noexcept {
    if (strong_.fetch_sub(1, std::memory_order_acq_rel) == 1) {
        destroy_object();
        release_weak();  // 释放强所有者持有的隐式弱引用
    }
}

shared_ptr(const shared_ptr& other) noexcept
    : pointer_(other.pointer_), control_(other.control_) {
    if (control_) control_->add_strong();
}

~shared_ptr() {
    if (control_) control_->release_strong();
}

这段代码展示最容易写错的状态转换。完整实现放在下一节,并与另外两种智能指针共用同一份 smart_ptrs.hpp


六、完整教学实现

namespace oc::handmade {

namespace detail {

struct adopt_strong_t {
    explicit adopt_strong_t() = default;
};

inline constexpr adopt_strong_t adopt_strong{};

class control_block {
public:
    control_block() = default;
    control_block(const control_block&) = delete;
    control_block& operator=(const control_block&) = delete;
    virtual ~control_block() = default;

    void add_strong() noexcept {
        strong_.fetch_add(1, std::memory_order_relaxed);
    }

    [[nodiscard]] bool try_add_strong() noexcept {
        std::size_t observed = strong_.load(std::memory_order_acquire);
        while (observed != 0) {
            if (strong_.compare_exchange_weak(
                    observed,
                    observed + 1,
                    std::memory_order_acquire,
                    std::memory_order_relaxed)) {
                return true;
            }
        }
        return false;
    }

    void release_strong() noexcept {
        if (strong_.fetch_sub(1, std::memory_order_acq_rel) == 1) {
            destroy_object();
            release_weak();  // release the implicit weak reference
        }
    }

    void add_weak() noexcept {
        weak_.fetch_add(1, std::memory_order_relaxed);
    }

    void release_weak() noexcept {
        if (weak_.fetch_sub(1, std::memory_order_acq_rel) == 1) {
            delete_self();
        }
    }

    [[nodiscard]] std::size_t use_count() const noexcept {
        return strong_.load(std::memory_order_acquire);
    }

private:
    virtual void destroy_object() noexcept = 0;
    virtual void delete_self() noexcept = 0;

    std::atomic<std::size_t> strong_{1};
    // Explicit weak owners plus one implicit weak reference while strong > 0.
    std::atomic<std::size_t> weak_{1};
};

template<class T, class Deleter>
class pointer_control_block final : public control_block {
public:
    pointer_control_block(T* pointer, Deleter deleter)
        : pointer_(pointer), deleter_(std::move(deleter)) {}

private:
    void destroy_object() noexcept override {
        if (pointer_) {
            deleter_(std::exchange(pointer_, nullptr));
        }
    }

    void delete_self() noexcept override { delete this; }

    T* pointer_;
    [[no_unique_address]] Deleter deleter_;
};

template<class T>
class inplace_control_block final : public control_block {
public:
    template<class... Args>
    explicit inplace_control_block(Args&&... args) {
        std::construct_at(get(), std::forward<Args>(args)...);
        alive_ = true;
    }

    [[nodiscard]] T* get() noexcept {
        return std::launder(reinterpret_cast<T*>(storage_));
    }

private:
    void destroy_object() noexcept override {
        if (alive_) {
            std::destroy_at(get());
            alive_ = false;
        }
    }

    void delete_self() noexcept override { delete this; }

    alignas(T) std::byte storage_[sizeof(T)];
    bool alive_{};
};

}  // namespace detail

template<class T>
class shared_ptr;

template<class T>
class weak_ptr;

template<class T, class... Args>
[[nodiscard]] shared_ptr<T> make_shared(Args&&... args);

template<class T>
class shared_ptr {
public:
    using element_type = T;

    constexpr shared_ptr() noexcept = default;
    constexpr shared_ptr(std::nullptr_t) noexcept {}

    template<class U>
        requires std::convertible_to<U*, T*>
    explicit shared_ptr(U* pointer) : pointer_(pointer) {
        if (!pointer) return;
        try {
            control_ = new detail::pointer_control_block<U, default_delete<U>>(
                pointer, default_delete<U>{});
        } catch (...) {
            default_delete<U>{}(pointer);
            pointer_ = nullptr;
            throw;
        }
    }

    shared_ptr(const shared_ptr& other) noexcept
        : pointer_(other.pointer_), control_(other.control_) {
        if (control_) control_->add_strong();
    }

    template<class U>
        requires std::convertible_to<U*, T*>
    shared_ptr(const shared_ptr<U>& other) noexcept
        : pointer_(other.pointer_), control_(other.control_) {
        if (control_) control_->add_strong();
    }

    template<class U>
    shared_ptr(const shared_ptr<U>& owner, T* alias) noexcept
        : pointer_(alias), control_(owner.control_) {
        if (control_) control_->add_strong();
    }

    shared_ptr(shared_ptr&& other) noexcept
        : pointer_(std::exchange(other.pointer_, nullptr)),
          control_(std::exchange(other.control_, nullptr)) {}

    template<class U>
        requires std::convertible_to<U*, T*>
    shared_ptr(shared_ptr<U>&& other) noexcept
        : pointer_(std::exchange(other.pointer_, nullptr)),
          control_(std::exchange(other.control_, nullptr)) {}

    explicit shared_ptr(const weak_ptr<T>& weak);

    ~shared_ptr() {
        if (control_) control_->release_strong();
    }

    shared_ptr& operator=(const shared_ptr& other) noexcept {
        shared_ptr(other).swap(*this);
        return *this;
    }

    shared_ptr& operator=(shared_ptr&& other) noexcept {
        shared_ptr(std::move(other)).swap(*this);
        return *this;
    }

    void reset() noexcept { shared_ptr().swap(*this); }

    template<class U>
        requires std::convertible_to<U*, T*>
    void reset(U* pointer) {
        shared_ptr(pointer).swap(*this);
    }

    void swap(shared_ptr& other) noexcept {
        using std::swap;
        swap(pointer_, other.pointer_);
        swap(control_, other.control_);
    }

    [[nodiscard]] T* get() const noexcept { return pointer_; }
    [[nodiscard]] T& operator*() const noexcept { return *pointer_; }
    [[nodiscard]] T* operator->() const noexcept { return pointer_; }
    [[nodiscard]] explicit operator bool() const noexcept {
        return pointer_ != nullptr;
    }
    [[nodiscard]] std::size_t use_count() const noexcept {
        return control_ ? control_->use_count() : 0;
    }

private:
    shared_ptr(detail::control_block* control, T* pointer, detail::adopt_strong_t)
        noexcept
        : pointer_(pointer), control_(control) {}

    template<class U>
    friend class shared_ptr;
    template<class U>
    friend class weak_ptr;
    template<class U, class... Args>
    friend shared_ptr<U> make_shared(Args&&... args);

    T* pointer_{};
    detail::control_block* control_{};
};

template<class T, class... Args>
[[nodiscard]] shared_ptr<T> make_shared(Args&&... args) {
    auto* control =
        new detail::inplace_control_block<T>(std::forward<Args>(args)...);
    return shared_ptr<T>(control, control->get(), detail::adopt_strong);
}

}  // namespace oc::handmade

统一测试覆盖移动、复制、数组、别名所有权、对象析构、过期观察者和失败提升;实现没有向 std 命名空间注入任何类型。


七、使用示例与输出

预期输出或状态:

复制后 use_count 从 1 变为 2;副本离开作用域后恢复为 1;最后一个强所有者析构时对象存活计数回到 0。

示例使用断言验证状态,因此成功时不打印额外文本。析构计数必须回到 0,才能说明资源和控制块路径已经闭合。


八、复杂度与失效规则

操作 复杂度 说明
默认/移动构造 O(1) 不修改或转移计数
复制构造/复制赋值 O(1) 原子增加强计数
reset/析构 O(1)+析构 最后一个所有者销毁对象
use_count O(1) 并发下只是瞬时快照
make_shared 一次分配+构造 对象与控制块同址
别名构造 O(1) 共享所有权但 get() 可不同

智能指针本身没有容器迭代器;这里的“失效”指资源销毁后,裸指针、引用或观察者能否继续安全使用。任何从 get()operator*operator-> 取得的非拥有访问,都不能延长对象生命周期。


九、异常安全与资源管理

  • 从裸指针建立控制块若分配失败,必须立即用匹配 deleter 回收原对象。
  • 复制赋值使用临时副本再 swap,先成功增加新计数,随后安全释放旧所有权。
  • make_shared 中对象构造失败时,new-expression 自动释放控制块存储。
  • 引用计数原子化只保护控制块;多个线程修改同一个 shared_ptr 对象或其指向对象仍需外部同步。

析构和引用计数释放路径必须为 noexcept。用户 deleter 若从析构路径抛出,程序无法可靠恢复,因此教学实现要求 deleter 的调用和移动不抛异常。


十、常见错误

1. 同一裸指针创建两个控制块

两个互不知情的强计数都会到零,最终造成双重释放。

2. 把 use_count 当同步条件

读取计数后其他线程可以立即复制或释放,它只能用于观测和调试。

3. 误解别名构造

别名指针可能指向子对象;销毁时释放的是控制块拥有的完整对象,而不是 get()。


十一、面试追问

  1. 控制块里通常保存哪些信息,为什么不只保存一个计数?
  2. make_shared 为什么通常只分配一次,它又有什么内存滞留代价?
  3. 不同 shared_ptr 实例并发复制为何安全,同一实例并发赋值为何不安全?
  4. 别名构造的所有权指针与观察指针有什么区别?

回答时先说明所有权,再画出对象与控制块,最后给出原子计数、异常和并发边界。


十二、练习与自测

  1. 为指针控制块增加自定义 deleter
  2. 实现受约束的派生类到基类复制转换
  3. 记录对象销毁与控制块释放的不同时间点

自测标准:能够不看代码画出每次复制、移动、reset、lock 和析构后的强弱计数,并解释最后由谁销毁对象、由谁释放控制块。


十三、官方资料与延伸阅读


上一篇:手写 std::unique_ptr | 下一篇:手写 std::weak_ptr