跳转到内容

C++ weak_ptr

来自代码酷
Admin留言 | 贡献2025年4月28日 (一) 21:30的版本 (Page creation by admin bot)

(差异) ←上一版本 | 已核准修订 (差异) | 最后版本 (差异) | 下一版本→ (差异)


C++ weak_ptr 是 C++ 标准库中提供的一种智能指针,用于解决共享所有权模型中的循环引用问题。它是 `std::shared_ptr` 的伴随指针,本身不增加引用计数,但可以安全地观察 `shared_ptr` 管理的对象是否存在。

介绍[编辑 | 编辑源代码]

`std::weak_ptr` 是一种不控制对象生命周期的智能指针,它指向一个由 `std::shared_ptr` 管理的对象。与 `shared_ptr` 不同,`weak_ptr` 不会增加对象的引用计数,因此不会阻止对象的销毁。其主要用途包括:

  • 打破 `shared_ptr` 之间的循环引用
  • 临时访问 `shared_ptr` 管理的对象(需通过 `lock()` 方法)
  • 观察资源是否存在而不影响其生命周期

基本用法[编辑 | 编辑源代码]

创建 weak_ptr[编辑 | 编辑源代码]

`weak_ptr` 通常从 `shared_ptr` 构造或赋值:

#include <memory>
#include <iostream>

int main() {
    std::shared_ptr<int> shared = std::make_shared<int>(42);
    std::weak_ptr<int> weak(shared);  // 从 shared_ptr 构造
    
    // 检查对象是否还存在
    if (auto temp = weak.lock()) {
        std::cout << "Value: " << *temp << "\n";  // 输出: Value: 42
    } else {
        std::cout << "Object already destroyed\n";
    }
    
    shared.reset();  // 释放所有权
    
    if (weak.expired()) {
        std::cout << "Object has been destroyed\n";  // 输出: Object has been destroyed
    }
}

主要成员函数[编辑 | 编辑源代码]

  • `lock()`: 返回一个 `shared_ptr`,若原对象存在则共享所有权,否则返回空
  • `expired()`: 检查被观察对象是否已被释放
  • `use_count()`: 返回共享该对象的 `shared_ptr` 数量

解决循环引用[编辑 | 编辑源代码]

循环引用是 `shared_ptr` 的常见问题,会导致内存泄漏。以下示例展示如何使用 `weak_ptr` 解决此问题:

问题示例[编辑 | 编辑源代码]

struct Node {
    std::shared_ptr<Node> next;
    ~Node() { std::cout << "Node destroyed\n"; }
};

int main() {
    auto node1 = std::make_shared<Node>();
    auto node2 = std::make_shared<Node>();
    
    node1->next = node2;  // node1 引用 node2
    node2->next = node1;  // node2 引用 node1 → 循环引用
    
    // 退出作用域时,引用计数不为0,内存泄漏!
}

解决方案[编辑 | 编辑源代码]

将其中一个指针改为 `weak_ptr`:

struct Node {
    std::weak_ptr<Node> next;  // 改为 weak_ptr
    ~Node() { std::cout << "Node destroyed\n"; }
};

int main() {
    auto node1 = std::make_shared<Node>();
    auto node2 = std::make_shared<Node>();
    
    node1->next = node2;
    node2->next = node1;  // 不会增加引用计数
    
    // 退出作用域时对象会被正确销毁
    // 输出: Node destroyed\nNode destroyed
}

实际应用场景[编辑 | 编辑源代码]

缓存系统[编辑 | 编辑源代码]

`weak_ptr` 适合实现对象缓存,当外部不再需要缓存项时允许自动清理:

class Cache {
    std::unordered_map<int, std::weak_ptr<Resource>> cache;
    
public:
    std::shared_ptr<Resource> get(int id) {
        if (auto it = cache.find(id); it != cache.end()) {
            if (auto res = it->second.lock()) {
                return res;  // 缓存命中
            }
        }
        // 缓存未命中,创建新资源
        auto res = std::make_shared<Resource>(id);
        cache[id] = res;
        return res;
    }
};

观察者模式[编辑 | 编辑源代码]

观察者可以使用 `weak_ptr` 观察主体,避免影响主体生命周期:

class Subject {
    std::vector<std::weak_ptr<Observer>> observers;
    
public:
    void notify() {
        for (auto& weak_obs : observers) {
            if (auto obs = weak_obs.lock()) {
                obs->update();
            }
        }
    }
};

性能与安全考虑[编辑 | 编辑源代码]

  • **性能**: `lock()` 操作包含原子操作,比裸指针访问稍慢
  • **线程安全**: 与 `shared_ptr` 类似,`weak_ptr` 的基本操作是线程安全的
  • **空指针**: 解引用 `weak_ptr` 前必须通过 `lock()` 获取 `shared_ptr`

与其它智能指针对比[编辑 | 编辑源代码]

指针类型 所有权 是否影响生命周期 主要用途
`unique_ptr` 独占所有权 单一所有者场景
`shared_ptr` 共享所有权 多所有者场景
`weak_ptr` 无所有权 解决循环引用/观察资源

高级主题[编辑 | 编辑源代码]

自定义删除器[编辑 | 编辑源代码]

`weak_ptr` 可以观察带有自定义删除器的 `shared_ptr`:

auto deleter = [](int* p) { 
    std::cout << "Custom delete\n"; 
    delete p; 
};

std::shared_ptr<int> shared(new int(10), deleter);
std::weak_ptr<int> weak(shared);

继承关系[编辑 | 编辑源代码]

`weak_ptr` 支持派生类到基类的转换:

struct Base {};
struct Derived : Base {};

std::shared_ptr<Derived> d = std::make_shared<Derived>();
std::weak_ptr<Base> b = d;  // 隐式转换

常见问题[编辑 | 编辑源代码]

何时使用 weak_ptr[编辑 | 编辑源代码]

  • 当需要观察对象但不应延长其生命周期时
  • 解决可能产生循环引用的设计场景
  • 实现缓存、观察者等模式

weak_ptr 能否单独使用[编辑 | 编辑源代码]

不能。`weak_ptr` 必须与 `shared_ptr` 配合使用,不能直接管理内存。

总结[编辑 | 编辑源代码]

`std::weak_ptr` 是 C++ 内存管理工具链中的重要组成部分,它:

  • 解决了 `shared_ptr` 的循环引用问题
  • 提供了安全观察共享对象的方式
  • 不会增加引用计数,不影响对象生命周期
  • 必须通过 `lock()` 方法访问实际对象

正确使用 `weak_ptr` 可以显著提高 C++ 程序的资源管理安全性和设计灵活性。

graph LR A[shared_ptr] -->|构造/赋值| B[weak_ptr] B -->|lock| C[shared_ptr] C -->|解引用| D[访问对象] B -->|expired| E[检查对象状态]