# 引用计数 · scoped_refptr 与对象的生死

> WebRTC 的对象在多线程间传来传去，谁负责销毁？答案是引用计数。这一章读 scoped_refptr.h / ref_counted_object.h，讲清 RefCountInterface、RefCountedObject、make_ref_counted、scoped_refptr 这套机制，以及它为什么是异步多线程代码的生命线。

- 作者：David（道雾轩）
- 专栏：音视频与 WebRTC 详解（https://daiw.org/manual/webrtc.md）
- 最后更新：2026-07-14
- 原文：https://daiw.org/manual/webrtc/scoped-refptr
- 转载与引用：请注明出处并附原文链接（https://daiw.org/about/copyright）

# 引用计数 · scoped_refptr 与对象的生死

[上一章](https://daiw.org/manual/webrtc/thread-and-taskqueue)说，WebRTC 靠 [`PostTask` 在线程间投递任务](https://daiw.org/manual/webrtc/thread-and-taskqueue)。但这带来一个尖锐问题：一个对象被[投递到别的线程异步使用](https://daiw.org/manual/webrtc/proxy-pattern)，**执行时它还活着吗**？会不会已经被原线程销毁了？WebRTC 的答案是**引用计数**——这一章看它。

## 问题：异步世界里，谁来销毁对象

同步单线程代码里，对象生死很清楚：出了作用域就析构。但 WebRTC 是[异步多线程](https://daiw.org/manual/webrtc/threading-model)的：

<Callout type="info">
  **在异步代码里，一个对象可能被**多个线程、多个回调**同时引用，“谁最后用完谁负责销毁”用手动 `delete` 根本管不清——所以 WebRTC 用**引用计数**：对象记录“有多少人正引用我”，降到 0 才自我销毁。** 想象 [`PeerConnection`](https://daiw.org/manual/webrtc/peer-connection-object) 被应用持有、又被[内部若干组件](https://daiw.org/manual/webrtc/peer-connection-object)引用、还被[投递到别的线程的任务](https://daiw.org/manual/webrtc/thread-and-taskqueue)捕获——它到底何时能销毁？没有一个中心知道全部引用。引用计数把这个判断**分散化**：每个持有者“登记”（计数 +1）、“注销”（计数 -1），对象自己在计数归 0 时析构。**没有谁需要知道全局，对象自己管自己的生死。** 这是异步多线程代码管理生命周期的经典解法（也是 `std::shared_ptr` 的思路）。
</Callout>

## 三个零件：接口、实现、智能指针

WebRTC 的引用计数由三部分组成（比 `shared_ptr` 更“侵入式”——计数长在对象自己身上）：

### 1. `RefCountInterface`：计数接口

```cpp
// api/ref_count.h(节选)
class RefCountInterface {
 public:
  virtual void AddRef() const = 0;
  virtual RefCountReleaseStatus Release() const = 0;   // 返回是否"刚丢掉最后一个引用"
};
```

任何想被引用计数管理的类，实现这个接口：`AddRef` 计数 +1，`Release` 计数 -1 并在归 0 时返回 `kDroppedLastRef`。

### 2. `RefCountedObject<T>`：自动加计数的实现

你不用手写计数逻辑，`RefCountedObject<T>` 帮你把原子计数**混入**任意类 `T`（真实源码 `ref_counted_object.h:21`）：

```cpp
// ref_counted_object.h(节选)
template <class T>
class RefCountedObject : public T {           // 继承 T,给它加上引用计数
  RefCountReleaseStatus Release() const override {
    // 原子减,归 0 时 delete this
    if (status == RefCountReleaseStatus::kDroppedLastRef) { delete this; }
    ...
  }
  mutable webrtc::webrtc_impl::RefCounter ref_count_{0};   // 原子计数器
};
```

通常用工厂函数 `make_ref_counted<MyClass>(args...)` 创建——它 `new` 一个 `RefCountedObject<MyClass>` 并返回 `scoped_refptr`。**原子计数**保证多线程 `AddRef`/`Release` 安全。

### 3. `scoped_refptr<T>`：RAII 智能指针

手动 `AddRef`/`Release` 容易忘、容易漏——所以几乎没人手动调。你用 **`scoped_refptr<T>`**，它在构造 / 拷贝时自动 `AddRef`、析构时自动 `Release`（真实源码 `scoped_refptr.h`）：

```cpp
// scoped_refptr.h(节选,精简)
template <typename T>
class scoped_refptr {
  scoped_refptr(const scoped_refptr& r) : ptr_(r.get()) { if (ptr_) ptr_->AddRef(); }
  ~scoped_refptr() { if (ptr_) ptr_->Release(); }      // 出作用域自动 -1
  scoped_refptr(scoped_refptr&& r) noexcept : ptr_(r.release()) {}  // 移动不加计数
  T* get() const { return ptr_; }
 private:
  T* ptr_;
};
```

<Callout type="info">
  **`scoped_refptr<T>` 是 WebRTC 版的 `shared_ptr`——**拷贝就 +1、析构就 -1、移动零成本**，你只管传递它，计数全自动。** 关键行为：

  - **拷贝 = 多一个引用** —— `scoped_refptr` 传参 / 赋值，计数 +1，对象因此“多活一会儿”。
  - **析构 = 少一个引用** —— 出作用域自动 -1，最后一个走的负责销毁。
  - **移动 = 转移所有权** —— `std::move` 一个 `scoped_refptr` 不改计数（`release()` 交出裸指针），零开销。

  于是[投递到别的线程的任务](https://daiw.org/manual/webrtc/thread-and-taskqueue)只要**捕获一个 `scoped_refptr`**，就等于给对象续了命——任务没跑完，计数就不为 0，对象就不会被销毁。**引用计数 + `scoped_refptr` 是 WebRTC 异步安全的生命线。**
</Callout>

## 为什么到处是 scoped_refptr

现在回头看[前面各部分](https://daiw.org/manual/webrtc/call-recap)，就懂了：[`PeerConnectionInterface`](https://daiw.org/manual/webrtc/peer-connection-object)、[`MediaStreamTrackInterface`](https://daiw.org/manual/webrtc/transceivers)、[`RtpSenderInterface`](https://daiw.org/manual/webrtc/rtp-sender)、[`RTCCertificate`](https://daiw.org/manual/webrtc/certificates-fingerprints)……WebRTC 的 API 对象**几乎清一色用 `scoped_refptr` 返回和传递**。原因就是上面说的：这些对象[跨线程、被多方持有、生命周期不确定](https://daiw.org/manual/webrtc/threading-model)，必须用引用计数管理。[`CreatePeerConnection` 返回 `scoped_refptr<PeerConnectionInterface>`](https://daiw.org/manual/webrtc/factory-and-context)——你现在知道这个类型不是随便选的，而是[异步架构](https://daiw.org/manual/webrtc/proxy-pattern)的必然。

```mermaid
flowchart LR
    APP["应用持有<br/>scoped_refptr"] -->|"+1"| OBJ["PeerConnection<br/>refcount=3"]
    COMP["内部组件持有<br/>scoped_refptr"] -->|"+1"| OBJ
    TASK["跨线程任务捕获<br/>scoped_refptr"] -->|"+1"| OBJ
    OBJ -->|"计数归 0 才"| DEL["自我销毁"]
```

## 小结与下一章

WebRTC 用**引用计数**管理[异步多线程](https://daiw.org/manual/webrtc/threading-model)下的对象生死：对象记“多少人引用我”、归 0 自毁，免去了“[谁负责 delete](https://daiw.org/manual/webrtc/proxy-pattern)”的难题。三个零件：**`RefCountInterface`**（`AddRef`/`Release` 接口）、**`RefCountedObject<T>`**（把原子计数混入任意类，归 0 `delete this`，配 `make_ref_counted`）、**`scoped_refptr<T>`**（RAII 智能指针：拷贝 +1、析构 -1、移动零成本）。[跨线程任务捕获 `scoped_refptr` 即给对象续命](https://daiw.org/manual/webrtc/thread-and-taskqueue)——这是 WebRTC 异步安全的生命线，也是[API 对象清一色 `scoped_refptr`](https://daiw.org/manual/webrtc/peer-connection-object) 的原因。

对象在系统里传递时，还有一样东西天天在传——**媒体数据缓冲**。它多到必须“零拷贝”。下一章看 WebRTC 的缓冲设计。👉 [零拷贝缓冲](https://daiw.org/manual/webrtc/buffers)。

## 参考资料

- [1] libwebrtc 源码.`api/scoped_refptr.h`、`api/ref_count.h`、`rtc_base/ref_counted_object.h`、`rtc_base/ref_counter.h`。
- [2] libwebrtc 源码.`api/make_ref_counted.h`（`make_ref_counted` 工厂）。
- [3] 关联[线程与任务队列](https://daiw.org/manual/webrtc/thread-and-taskqueue)、[代理模式](https://daiw.org/manual/webrtc/proxy-pattern)、[工厂](https://daiw.org/manual/webrtc/factory-and-context)。
