C++11 weak_ptr 原理与详细教程

发布于:2025-07-05 ⋅ 阅读:(17) ⋅ 点赞:(0)

weak_ptrshared_ptr的点睛之笔!

一、weak_ptr 概述

std::weak_ptr 是 C++11 引入的一种非拥有型智能指针,它与 std::shared_ptr 配合使用,用于解决共享所有权场景下的循环引用问题。weak_ptr 本身不拥有对象的所有权,仅提供对 shared_ptr 管理对象的临时访问能力,不会影响对象的生命周期。

核心优势

  • 打破循环引用:解决 shared_ptr 形成的循环引用导致的内存泄漏
  • 非侵入式观察:不增加引用计数,不影响对象的销毁时机
  • 安全访问:提供 lock() 方法安全获取对象访问权,避免悬垂指针
  • 轻量级设计:仅需存储控制块指针,开销小

二、weak_ptr 原理深度解析

2.1 弱引用计数机制

weak_ptr 依赖 shared_ptr 的控制块实现功能,控制块中包含两种计数:

  • 共享引用计数:跟踪管理对象的 shared_ptr 数量
  • 弱引用计数:跟踪观察对象的 weak_ptr 数量

weak_ptr 的核心工作流程:

  1. shared_ptr 构造时,弱引用计数增加 1
  2. weak_ptr 销毁时,弱引用计数减少 1
  3. 当共享引用计数变为 0 时,对象被销毁,但控制块保留
  4. 当弱引用计数也变为 0 时,控制块被释放

2.2 内存布局

weak_ptr 通常包含一个指针大小的成员:

  • 指向控制块的指针:与 shared_ptr 共享同一个控制块

![weak_ptr与shared_ptr内存布局关系图]

2.3 简化版 weak_ptr 实现

基于之前实现的 SharedPtr,下面实现简化版 WeakPtr

#include <atomic>
#include <utility>

// 前向声明
template <typename T>
class SharedPtr;

// 控制块基类(与之前SharedPtr实现相同)
class ControlBlockBase {
public:
    std::atomic<int> shared_count;  // 共享引用计数
    std::atomic<int> weak_count;    // 弱引用计数

    ControlBlockBase() : shared_count(1), weak_count(0) {}
    virtual ~ControlBlockBase() = default;
    virtual void destroy() = 0;     // 销毁管理对象
    virtual void deallocate() = 0;  // 释放控制块
};

// 简化版 weak_ptr 实现
template <typename T>
class WeakPtr {
private:
    T* ptr;                  // 存储的指针(与对应的shared_ptr相同)
    ControlBlockBase* control_block; // 控制块指针

    // 增加弱引用计数
    void increase_weak_count() {
        if (control_block) {
            control_block->weak_count.fetch_add(1, std::memory_order_relaxed);
        }
    }

    // 减少弱引用计数并检查是否需要释放控制块
    void decrease_weak_count() {
        if (control_block) {
            if (control_block->weak_count.fetch_sub(1, std::memory_order_acq_rel) == 1) {
                // 弱引用计数为0,且共享引用计数也为0时释放控制块
                if (control_block->shared_count == 0) {
                    control_block->deallocate();
                }
            }
        }
    }

public:
    // 默认构造函数
    WeakPtr() : ptr(nullptr), control_block(nullptr) {}

    // 从 shared_ptr 构造
    WeakPtr(const SharedPtr<T>& shared_ptr) 
        : ptr(shared_ptr.ptr), control_block(shared_ptr.control_block) {
        increase_weak_count();
    }

    // 拷贝构造函数
    WeakPtr(const WeakPtr& other) 
        : ptr(other.ptr), control_block(other.control_block) {
        increase_weak_count();
    }

    // 移动构造函数
    WeakPtr(WeakPtr&& other) noexcept 
        : ptr(other.ptr), control_block(other.control_block) {
        other.ptr = nullptr;
        other.control_block = nullptr;
    }

    // 析构函数
    ~WeakPtr() {
        decrease_weak_count();
    }

    // 拷贝赋值运算符
    WeakPtr& operator=(const WeakPtr& other) {
        if (this != &other) {
            decrease_weak_count();          // 减少当前弱引用计数
            ptr = other.ptr;
            control_block = other.control_block;
            increase_weak_count();          // 增加新的弱引用计数
        }
        return *this;
    }

    // 从 shared_ptr 赋值
    WeakPtr& operator=(const SharedPtr<T>& shared_ptr) {
        decrease_weak_count();              // 减少当前弱引用计数
        ptr = shared_ptr.ptr;
        control_block = shared_ptr.control_block;
        increase_weak_count();              // 增加新的弱引用计数
        return *this;
    }

    // 移动赋值运算符
    WeakPtr& operator=(WeakPtr&& other) noexcept {
        if (this != &other) {
            decrease_weak_count();          // 减少当前弱引用计数
            ptr = other.ptr;
            control_block = other.control_block;
            other.ptr = nullptr;
            other.control_block = nullptr;
        }
        return *this;
    }

    // 检查对象是否已过期(共享引用计数为0)
    bool expired() const noexcept {
        if (control_block) {
            return control_block->shared_count.load(std::memory_order_acquire) == 0;
        }
        return true; // 无控制块,视为已过期
    }

    // 获取共享指针,如果对象已过期则返回空
    SharedPtr<T> lock() const noexcept {
        if (!expired()) {
            // 创建shared_ptr,增加共享引用计数
            return SharedPtr<T>(ptr, control_block);
        }
        return SharedPtr<T>(); // 返回空shared_ptr
    }

    // 获取共享引用计数
    long use_count() const noexcept {
        if (control_block) {
            return control_block->shared_count.load(std::memory_order_relaxed);
        }
        return 0;
    }

    // 重置weak_ptr,不再指向任何对象
    void reset() noexcept {
        decrease_weak_count();
        ptr = nullptr;
        control_block = nullptr;
    }

    // 交换两个weak_ptr
    void swap(WeakPtr& other) noexcept {
        std::swap(ptr, other.ptr);
        std::swap(control_block, other.control_block);
    }

    // 友元类声明
    friend class SharedPtr<T>;
};

// 辅助函数:交换两个weak_ptr
template <typename T>
void swap(WeakPtr<T>& lhs, WeakPtr<T>& rhs) noexcept {
    lhs.swap(rhs);
}

2.4 关键技术点解析

  1. 弱引用计数管理

    • weak_ptr 创建时增加弱引用计数
    • 销毁时减少弱引用计数
    • 只有当共享引用计数和弱引用计数都为0时,控制块才被释放
  2. 安全访问机制

    • lock() 方法是访问对象的唯一安全方式
    • 内部检查共享引用计数,确保对象存在后才创建 shared_ptr
    • 返回的 shared_ptr 会增加共享引用计数,延长对象生命周期
  3. 与 shared_ptr 的协同

    • 共享同一个控制块,实现信息互通
    • weak_ptr 不影响对象生命周期,但能感知对象是否存活
    • 解决循环引用的核心是 weak_ptr 不增加共享引用计数

三、weak_ptr 使用详解

3.1 基本用法

#include <memory>
#include <iostream>

struct MyClass {
    MyClass(int id) : id(id) { 
        std::cout << "MyClass(" << id << ") constructed\n"; 
    }
    ~MyClass() { 
        std::cout << "MyClass(" << id << ") destroyed\n"; 
    }
    int id;
};

void observe(const std::weak_ptr<MyClass>& wp) {
    // 尝试获取共享指针
    if (auto sp = wp.lock()) {
        std::cout << "Observing MyClass(" << sp->id << "), use_count: " << sp.use_count() << "\n";
    } else {
        std::cout << "Observed object has been destroyed\n";
    }
}

int main() {
    std::weak_ptr<MyClass> wp;
    
    {
        auto sp = std::make_shared<MyClass>(1);
        wp = sp; // 从shared_ptr赋值给weak_ptr
        
        std::cout << "Inside scope, use_count: " << wp.use_count() << "\n"; // 1
        observe(wp); // 可以观察到对象
    } // sp销毁,共享引用计数变为0,对象被销毁
    
    std::cout << "Outside scope, use_count: " << wp.use_count() << "\n"; // 0
    observe(wp); // 对象已销毁,无法观察
    
    return 0;
}

输出结果:

MyClass(1) constructed
Inside scope, use_count: 1
Observing MyClass(1), use_count: 2
MyClass(1) destroyed
Outside scope, use_count: 0
Observed object has been destroyed

3.2 解决循环引用

weak_ptr 最主要的应用是解决 shared_ptr 的循环引用问题:

#include <memory>

// 存在循环引用的情况
struct Node {
    int data;
    std::shared_ptr<Node> next;  // 共享指针导致循环引用
};

int main() {
    auto node1 = std::make_shared<Node>(1);
    auto node2 = std::make_shared<Node>(2);
    
    node1->next = node2;
    node2->next = node1;  // 形成循环引用
    
    // 此时 node1 和 node2 的引用计数都是2
    std::cout << "node1 use_count: " << node1.use_count() << "\n"; // 2
    std::cout << "node2 use_count: " << node2.use_count() << "\n"; // 2
    
    // 离开作用域后,引用计数减为1,不会销毁对象,导致内存泄漏
    return 0;
}

使用 weak_ptr 打破循环引用:

struct Node {
    int data;
    std::weak_ptr<Node> next;  // 使用弱指针打破循环
};

int main() {
    auto node1 = std::make_shared<Node>(1);
    auto node2 = std::make_shared<Node>(2);
    
    node1->next = node2;
    node2->next = node1;  // 不会增加引用计数
    
    // 此时 node1 和 node2 的引用计数都是1
    std::cout << "node1 use_count: " << node1.use_count() << "\n"; // 1
    std::cout << "node2 use_count: " << node2.use_count() << "\n"; // 1
    
    // 访问弱指针指向的对象
    if (auto next = node1->next.lock()) {
        std::cout << "node1 next data: " << next->data << "\n"; // 2
    }
    
    return 0;
} // 引用计数减为0,对象被正确销毁

3.3 缓存与观察者模式

weak_ptr 适合实现缓存和观察者模式,仅在对象存在时才访问:

#include <unordered_map>
#include <string>

// 简单的对象缓存
template <typename T>
class ObjectCache {
private:
    std::unordered_map<std::string, std::weak_ptr<T>> cache;

public:
    // 获取对象,如果不存在则创建
    std::shared_ptr<T> get(const std::string& key) {
        auto it = cache.find(key);
        if (it != cache.end()) {
            if (auto sp = it->second.lock()) {
                return sp; // 缓存命中
            }
            cache.erase(it); // 缓存已过期,移除
        }
        
        // 创建新对象并缓存
        auto sp = std::make_shared<T>();
        cache[key] = sp;
        return sp;
    }
};

// 使用示例
struct Resource {};

int main() {
    ObjectCache<Resource> cache;
    
    auto res1 = cache.get("res1"); // 创建新对象
    auto res2 = cache.get("res1"); // 从缓存获取
    
    std::cout << "res1 use_count: " << res1.use_count() << "\n"; // 2
    
    res1.reset();
    res2.reset(); // 引用计数为0,对象销毁,缓存项过期
    
    auto res3 = cache.get("res1"); // 重新创建对象
    std::cout << "res3 use_count: " << res3.use_count() << "\n"; // 1
    
    return 0;
}

四、高级应用场景

4.1 观察者模式实现

weak_ptr 是实现观察者模式的理想选择,观察者可以安全观察主题对象,不影响其生命周期:

#include <vector>
#include <algorithm>

// 主题类
class Subject {
private:
    std::vector<std::weak_ptr<class Observer>> observers;

public:
    void attach(const std::shared_ptr<Observer>& observer);
    void notify();
};

// 观察者类
class Observer : public std::enable_shared_from_this<Observer> {
private:
    Subject& subject;

public:
    Observer(Subject& s) : subject(s) {
        subject.attach(shared_from_this());
    }
    
    virtual void update() = 0;
};

void Subject::attach(const std::shared_ptr<Observer>& observer) {
    observers.push_back(observer);
}

void Subject::notify() {
    // 遍历所有观察者,只通知存活的观察者
    auto it = observers.begin();
    while (it != observers.end()) {
        if (auto observer = it->lock()) {
            observer->update();
            ++it;
        } else {
            // 移除过期的观察者
            it = observers.erase(it);
        }
    }
}

// 具体观察者实现
class ConcreteObserver : public Observer {
private:
    int id;

public:
    ConcreteObserver(Subject& s, int id) : Observer(s), id(id) {}
    
    void update() override {
        std::cout << "Observer " << id << " updated\n";
    }
};

int main() {
    Subject subject;
    
    {
        auto observer1 = std::make_shared<ConcreteObserver>(subject, 1);
        auto observer2 = std::make_shared<ConcreteObserver>(subject, 2);
        subject.notify(); // 通知两个观察者
    } // observer1和observer2销毁
    
    subject.notify(); // 没有观察者被通知(都已过期)
    
    return 0;
}

4.2 与 enable_shared_from_this 配合使用

std::enable_shared_from_this 允许对象创建指向自身的 shared_ptr,结合 weak_ptr 可安全实现对象间的相互引用:

#include <memory>

class MyClass : public std::enable_shared_from_this<MyClass> {
public:
    std::weak_ptr<MyClass> get_weak_ptr() {
        return shared_from_this(); // 返回指向自身的weak_ptr
    }
    
    void do_something() {
        std::cout << "MyClass doing something\n";
    }
};

int main() {
    auto sp = std::make_shared<MyClass>();
    std::weak_ptr<MyClass> wp = sp->get_weak_ptr();
    
    if (auto p = wp.lock()) {
        p->do_something(); // 安全访问
    }
    
    return 0;
}

注意:不要在构造函数中使用 shared_from_this(),因为此时 shared_ptr 尚未完全构造。

五、注意事项与最佳实践

5.1 避免的操作

  1. 不要使用 expired() 判断后直接使用原始指针

    // 错误示例:存在竞态条件
    if (!wp.expired()) {
        // 可能在expired()和get()之间对象被销毁
        T* ptr = wp.lock().get(); 
        ptr->do_something(); // 危险!可能是悬垂指针
    }
    

    正确做法:始终通过 lock() 获取 shared_ptr 后再访问

  2. 不要存储 lock() 返回的临时 shared_ptr

    // 不推荐:延长了对象生命周期
    std::shared_ptr<T> sp = wp.lock();
    // ... 长时间操作 ...
    sp->do_something();
    

    正确做法:仅在需要访问对象时才 lock(),并尽快释放

  3. 不要从原始指针构造 weak_ptr

    // 错误示例:weak_ptr必须从shared_ptr构造
    T* raw_ptr = new T();
    std::weak_ptr<T> wp(raw_ptr); // 编译错误!没有这样的构造函数
    

5.2 最佳实践

  1. 始终通过 lock() 访问对象

    // 正确用法
    if (auto sp = wp.lock()) {
        sp->do_something(); // 安全访问
    } else {
        // 处理对象已销毁的情况
    }
    
  2. 循环引用检测

    • 在包含双向引用的结构中(如链表、树),使用 weak_ptr 作为反向引用
    • 父节点持有子节点的 shared_ptr,子节点持有父节点的 weak_ptr
  3. 缓存实现

    • 使用 weak_ptr 存储缓存对象,自动清理过期条目
    • 结合 lock() 检查对象有效性,不存在时重新创建
  4. 观察者模式

    • 主题存储观察者的 weak_ptr
    • 通知前检查观察者是否存活,自动移除过期观察者
  5. 线程安全注意事项

    • weak_ptrlock()expired() 是线程安全的
    • 但返回的 shared_ptr 所指向的对象仍需同步保护

六、weak_ptr 与其他智能指针对比

特性 weak_ptr shared_ptr unique_ptr
所有权 无所有权 共享所有权 独占所有权
引用计数 弱引用计数 共享引用计数
大小 一个指针大小 两个指针大小 一个指针大小
主要用途 解决循环引用、观察对象 共享资源 独占资源
访问对象 需通过 lock() 获取 shared_ptr 直接访问 直接访问
线程安全 是(引用计数操作) 是(引用计数操作)

选择建议

  • 当需要共享资源时,使用 shared_ptr
  • 当需要独占资源时,使用 unique_ptr(性能更优)
  • 当需要观察 shared_ptr 管理的资源且不影响其生命周期时,使用 weak_ptr
  • shared_ptr 形成循环引用时,将其中一个改为 weak_ptr

七、总结

std::weak_ptr 是 C++ 智能指针体系中的重要组成部分,专为配合 std::shared_ptr 解决特定问题而设计。其核心特点包括:

  • 非所有权引用:不拥有对象,不影响对象生命周期
  • 循环引用解决方案:打破 shared_ptr 形成的引用循环
  • 安全访问机制:通过 lock() 方法安全获取对象访问权
  • 轻量级设计:仅需存储控制块指针,开销小

掌握 weak_ptr 的使用需要理解其与 shared_ptr 的协同工作机制,特别是弱引用计数的管理方式。在实际开发中,weak_ptr 主要用于实现缓存、观察者模式以及解决循环引用问题,是编写健壮 C++ 代码的重要工具。

智能指针是现代 C++ 资源管理的基石,合理选择和使用 unique_ptrshared_ptrweak_ptr,能够有效避免内存泄漏,提高代码的安全性和可维护性。


网站公告

今日签到

点亮在社区的每一天
去签到