Skip to content

weak_ptr_factory - 弱引用工厂

WeakPtrFactory<T> 是弱引用机制的核心工厂类,负责创建和管理某个对象的弱引用。每个需要对外提供弱引用的对象都应该持有一个 WeakPtrFactory 成员,把它作为接口的守门员。

基本用法

在类中添加弱引用支持很简单,声明一个 WeakPtrFactory 成员即可:

cpp
#include "base/weak_ptr/weak_ptr_factory.h"

class NetworkManager {
public:
    void SendRequest(const std::string& url) {
        // 发送请求...
    }

    // 对外提供弱引用获取接口
    cf::WeakPtr<NetworkManager> GetWeakPtr() {
        return weak_factory_.GetWeakPtr();
    }

private:
    // 必须是最后一个成员变量
    cf::WeakPtrFactory<NetworkManager> weak_factory_{this};
};
```text

构造时传入 `this` 指针,工厂会记住对象的位置。每次调用 `GetWeakPtr()` 就会创建一个新的弱引用,指向同一个对象。

## 成员变量顺序

⚠️ 这是使用 `WeakPtrFactory` 最重要的规则:**必须声明为类的最后一个成员**

原因在于 C++ 的析构顺序——成员变量按照声明相反的顺序销毁。工厂最后声明,所以最先销毁,这样其他成员析构时如果持有自己的弱引用,能正确检测到失效:

```cpp
class MyClass {
private:
    // 这些成员先销毁
    std::vector<Callback> callbacks_;
    std::mutex mutex_;

    // 工厂最后销毁
    cf::WeakPtrFactory<MyClass> weak_factory_{this};
};
```text

如果把工厂放在中间,某些成员析构时可能仍然检测到弱引用"有效",然后尝试访问已经被析构的部分对象,后果是未定义行为。

## 创建弱引用

通过 `GetWeakPtr()` 创建弱引用:

```cpp
MyClass obj;

// 创建多个弱引用
auto weak1 = obj.GetWeakPtr();
auto weak2 = obj.GetWeakPtr();
auto weak3 = obj.GetWeakPtr();

// 所有弱引用都指向同一个对象
assert(weak1.Get() == weak2.Get());
assert(weak2.Get() == weak3.Get());
```text

每次调用都创建一个新的 `WeakPtr` 对象,但它们共享同一个内部的"存活标志"。对象销毁或调用 `InvalidateWeakPtrs()` 后,所有弱引用同时失效。存活标志(`WeakReferenceFlag`)直接嵌入在 `WeakPtrFactory` 内部,不涉及任何堆分配。所有 `WeakPtr` 实例通过裸指针引用这个标志,因此 `WeakPtr` 的创建开销极小。

## 手动失效

有时候你想显式让所有弱引用失效,而不是真的销毁对象:

```cpp
class ConfigManager {
public:
    void Reload() {
        // 让所有旧的弱引用失效
        weak_factory_.InvalidateWeakPtrs();

        // 重新加载配置...
    }

    cf::WeakPtr<ConfigManager> GetWeakPtr() {
        return weak_factory_.GetWeakPtr();
    }

private:
    cf::WeakPtrFactory<ConfigManager> weak_factory_{this};
};
```text

`InvalidateWeakPtrs()` 会把内部的存活标志设为失效。失效前创建的所有弱引用都会变成无效。注意,与旧版实现不同,失效后**不能再创建新的弱引用**——后续调用 `GetWeakPtr()` 会触发断言失败。这是因为标志直接嵌入在工厂中,失效操作只是将 `std::atomic<bool>` 设为 `false`,不会分配新的标志。

这个功能在对象"重置""关闭"时很有用——你想通知所有观察者对象不再可用,但对象本身还在。如果你需要"重启"后继续创建弱引用,应该考虑使用一个全新的工厂实例。

## 不可复制不可移动

`WeakPtrFactory` 禁止复制和移动:

```cpp
class MyClass {
private:
    cf::WeakPtrFactory<MyClass> weak_factory_{this};
};

MyClass a;
MyClass b = a;  // 编译错误:WeakPtrFactory 不可复制
MyClass c = std::move(a);  // 编译错误:WeakPtrFactory 不可移动
```text

这个设计是有意为之的。工厂和对象的生命周期绑定在一起,复制或移动会破坏这个关系。如果你确实需要移动对象,得先清理所有弱引用,但这个场景在我们的使用中极少出现,干脆直接禁了。

## 常见使用模式

### 回调管理

```cpp
class AsyncWorker {
public:
    using Callback = std::function<void(int)>;

    void DoWork(Callback callback) {
        // 保存弱引用而不是裸指针
        callbacks_.push_back([weak = GetWeakPtr(), callback](int result) {
            if (weak) {  // 检查对象是否存活
                callback(result);
            }
        });

        // 实际的工作...
    }

private:
    std::vector<Callback> callbacks_;
    cf::WeakPtrFactory<AsyncWorker> weak_factory_{this};
};
```text

### 观察者模式

```cpp
class Subject {
public:
    void AddObserver(cf::WeakPtr<Observer> observer) {
        observers_.push_back(observer);
    }

    void NotifyEvent() {
        // 自动清理已失效的观察者
        auto it = std::remove_if(observers_.begin(), observers_.end(),
            [](const auto& weak) { return !weak.IsValid(); });
        observers_.erase(it, observers_.end());

        // 通知剩余的观察者
        for (auto& weak : observers_) {
            if (weak) {
                weak->OnEvent();
            }
        }
    }

private:
    std::vector<cf::WeakPtr<Observer>> observers_;
    cf::WeakPtrFactory<Subject> weak_factory_{this};
};
```text

### 单次失效模式

由于 `InvalidateWeakPtrs()` 后不能再创建新的弱引用,推荐的使用模式是"失效即终局"——调用一次 `InvalidateWeakPtrs()` 表示对象进入不可用状态,之后不再创建新的弱引用:

```cpp
class ResourceManager {
public:
    void UnloadResource(const std::string& id) {
        // 让所有弱引用失效,之后不能再创建新的
        weak_factory_.InvalidateWeakPtrs();

        // 安全地清理资源,所有外部弱引用已失效
        resources_.erase(id);
    }

    // 注意:UnloadResource 后 GetWeakPtr() 会断言失败
    cf::WeakPtr<ResourceManager> GetWeakPtr() {
        return weak_factory_.GetWeakPtr();
    }

private:
    cf::WeakPtrFactory<ResourceManager> weak_factory_{this};
};
```text

## 注意事项

第一,不要在构造函数里就把 `this` 传给其他地方。对象构造完成前,其他成员还没初始化,通过弱引用访问会导致未定义行为。`WeakPtrFactory` 的构造函数里有断言检查 `this` 非空,但不会检查构造是否完成。

第二,不要在析构函数里调用 `GetWeakPtr()`。对象已经在销毁过程中,创建新的弱引用没有意义。`WeakPtrFactory` 的析构函数会自动调用 `InvalidateWeakPtrs()`,之后 `GetWeakPtr()` 会断言失败。

第三,存活标志使用 `std::atomic<bool>` 实现,`IsAlive()` 和 `Invalidate()` 本身是线程安全的。但 `WeakPtr` 的"检查有效性 + 访问对象"两步操作不是原子的,仍然需要在同一线程(或序列)中使用。

第四,所有 `WeakPtr` 实例的生命周期不能超过工厂。因为 `WeakPtr` 持有的是指向工厂内嵌标志的裸指针(不是 `shared_ptr`),工厂析构后,任何对 `WeakPtr` 的访问都是未定义行为。这也是为什么工厂必须声明为最后一个成员——确保标志在其他成员的析构函数运行之前就失效,而不是等到工厂自身析构。

## 与标准库的对比

`WeakPtrFactory` 没有标准库的直接对应物。`std::enable_shared_from_this` 提供了类似的功能,但它是配合 `shared_ptr` 使用的,而我们假设对象有明确的唯一拥有者。内部标志直接嵌入在工厂中(无堆分配),`WeakPtr` 通过裸指针引用该标志,整体开销比 `shared_ptr` 方案小得多。

如果你需要在已有代码中引入 `WeakPtrFactory`,最简单的迁移方式是把原来持有裸指针或引用的地方改成持有 `WeakPtr`。这通常不需要大幅改动调用代码,只需要在使用前检查有效性。

## 相关文档

- [weak_ptr - 弱引用指针](./weak_ptr.md)
- [ScopeGuard - 作用域守卫](./scope_guard.md)
- [基础工具类概述](./overview.md)

Built with VitePress