C++--weak_ptr

std::weak_ptr 是 C++ 标准库 <memory> 头文件中提供的一种智能指针,它是对 std::shared_ptr 的一种扩展。std::weak_ptr 并不拥有对象的所有权,它只是对 std::shared_ptr 所管理的对象的一种弱引用。也就是说,std::weak_ptr 不会影响所指向对象的引用计数,即使有多个 std::weak_ptr 指向同一个对象,该对象的引用计数也不会增加。

作用

  • 打破循环引用 :当两个或多个对象通过 std::shared_ptr 相互引用时,会形成循环引用,导致引用计数永远不会降为 0,从而造成内存泄漏。std::weak_ptr 可以打破这种循环引用,因为它不增加引用计数,当其他 std::shared_ptr 不再引用对象时,对象的引用计数可以降为 0 并被正确释放。
  • 安全地观测对象std::weak_ptr 可以用于安全地观测 std::shared_ptr 所管理的对象。在某些情况下,我们可能需要知道某个对象是否还存在,但又不想影响其生命周期,这时就可以使用 std::weak_ptr

使用场景

  • 解决循环引用问题 :在复杂的对象关系中,对象之间可能存在相互引用的情况,使用 std::weak_ptr 可以避免循环引用导致的内存泄漏。例如,在树形结构或图结构中,节点之间的引用可以使用 std::weak_ptr 来打破循环。
  • 实现缓存机制 :在缓存系统中,我们可能希望缓存对象在不再被其他地方使用时能够自动释放,同时又能在需要时检查缓存是否仍然有效。使用 std::weak_ptr 可以实现这种缓存机制。

用法举例

1. 打破循环引用

c 复制代码
#include <iostream>
#include <memory>

class B;

class A {
public:
    std::shared_ptr<B> b_ptr;
    ~A() { std::cout << "A destroyed" << std::endl; }
};

class B {
public:
    std::weak_ptr<A> a_ptr; // 使用 std::weak_ptr 打破循环引用
    ~B() { std::cout << "B destroyed" << std::endl; }
};

int main() {
    std::shared_ptr<A> a = std::make_shared<A>();
    std::shared_ptr<B> b = std::make_shared<B>();
    a->b_ptr = b;
    b->a_ptr = a;
    return 0;
}

在这个例子中,A 类持有一个 std::shared_ptr<B> 指向 B 对象,而 B 类持有一个 std::weak_ptr<A> 指向 A 对象。这样,当 ab 离开 main 函数的作用域时,A 对象的引用计数会降为 0,从而被正确销毁,然后 B 对象的引用计数也会降为 0 并被销毁。

2. 安全地观测对象

c 复制代码
#include <iostream>
#include <memory>

class MyClass {
public:
    MyClass() { std::cout << "MyClass constructor" << std::endl; }
    ~MyClass() { std::cout << "MyClass destructor" << std::endl; }
    void doSomething() { std::cout << "Doing something..." << std::endl; }
};

int main() {
    std::shared_ptr<MyClass> sharedPtr = std::make_shared<MyClass>();
    std::weak_ptr<MyClass> weakPtr = sharedPtr;

    // 检查对象是否仍然存在
    if (auto lockedPtr = weakPtr.lock()) {
        lockedPtr->doSomething();
    } else {
        std::cout << "Object is no longer available." << std::endl;
    }

    // 释放 sharedPtr,对象被销毁
    sharedPtr.reset();

    // 再次检查对象是否仍然存在
    if (auto lockedPtr = weakPtr.lock()) {
        lockedPtr->doSomething();
    } else {
        std::cout << "Object is no longer available." << std::endl;
    }

    return 0;
}

在这个例子中,首先创建一个 std::shared_ptr 管理 MyClass 对象,然后创建一个 std::weak_ptr 指向该对象。使用 weakPtr.lock() 方法可以尝试获取一个 std::shared_ptr 指向该对象,如果对象仍然存在,则返回一个有效的 std::shared_ptr;如果对象已经被销毁,则返回一个空的 std::shared_ptr。在释放 sharedPtr 后,再次调用 weakPtr.lock() 会返回一个空的 std::shared_ptr,表示对象已经不存在。

注意:

1. 不能直接访问所指向的对象

  • 原因std::weak_ptr 本身并不拥有对象的所有权,它只是对 std::shared_ptr 所管理对象的一个弱引用,因此不能直接通过 std::weak_ptr 来访问对象的成员。
  • 解决办法 :要访问对象,需要调用 std::weak_ptrlock() 方法,该方法会返回一个 std::shared_ptr,如果对象还存在,这个 std::shared_ptr 就可以安全地用于访问对象;如果对象已经被销毁,lock() 会返回一个空的 std::shared_ptr
  • 示例代码
c 复制代码
#include <iostream>
#include <memory>

class MyClass {
public:
    void print() { std::cout << "Hello from MyClass!" << std::endl; }
};

int main() {
    std::shared_ptr<MyClass> sharedPtr = std::make_shared<MyClass>();
    std::weak_ptr<MyClass> weakPtr = sharedPtr;

    // 通过 lock() 获取 shared_ptr 来访问对象
    if (auto lockedPtr = weakPtr.lock()) {
        lockedPtr->print();
    }
    return 0;
}

2. 检查对象是否存在

  • 原因 :由于 std::weak_ptr 不会影响对象的生命周期,在使用 lock() 方法之前,需要确认对象是否仍然存在,否则可能会得到一个空的 std::shared_ptr,对其进行操作会导致未定义行为。
  • 示例代码
c 复制代码
#include <iostream>
#include <memory>

class MyClass {
public:
    void print() { std::cout << "Hello from MyClass!" << std::endl; }
};

int main() {
    std::shared_ptr<MyClass> sharedPtr = std::make_shared<MyClass>();
    std::weak_ptr<MyClass> weakPtr = sharedPtr;

    sharedPtr.reset(); // 释放对象

    if (auto lockedPtr = weakPtr.lock()) {
        lockedPtr->print();
    } else {
        std::cout << "Object is no longer available." << std::endl;
    }
    return 0;
}

3. 生命周期依赖

  • 原因std::weak_ptr 的有效性完全依赖于其所指向的 std::shared_ptr。如果 std::shared_ptr 所管理的对象被销毁,std::weak_ptr 就会变成无效的,后续调用 lock() 会返回空的 std::shared_ptr
  • 注意事项 :在使用 std::weak_ptr 时,要确保在需要使用它的整个时间段内,至少有一个 std::shared_ptr 持有该对象的所有权,否则可能会出现访问空指针的问题。

4. 性能开销

  • 原因std::weak_ptrlock() 操作会涉及到引用计数的检查和可能的原子操作,这会带来一定的性能开销。特别是在高并发场景下,频繁调用 lock() 可能会影响性能。
  • 建议 :如果需要频繁访问对象,尽量减少 lock() 的调用次数,可以在一次 lock() 成功后,将返回的 std::shared_ptr 保存起来,在需要的地方使用这个 std::shared_ptr 进行操作。

5. 线程安全

  • 原因std::weak_ptr 的引用计数操作是线程安全的,但对其所指向对象的访问不是线程安全的。如果多个线程同时对 std::weak_ptr 进行 lock() 操作并访问对象,可能会导致数据竞争。
  • 解决办法 :在多线程环境中,需要对对象的访问进行同步。可以使用互斥锁(如 std::mutex)来保护对对象的访问。
  • 示例代码
arduino 复制代码
#include <iostream>
#include <memory>
#include <thread>
#include <mutex>

class MyClass {
public:
    std::mutex mtx;
    void print() {
        std::lock_guard<std::mutex> lock(mtx);
        std::cout << "Hello from MyClass!" << std::endl;
    }
};

void worker(std::weak_ptr<MyClass> weakPtr) {
    if (auto lockedPtr = weakPtr.lock()) {
        lockedPtr->print();
    }
}

int main() {
    std::shared_ptr<MyClass> sharedPtr = std::make_shared<MyClass>();
    std::weak_ptr<MyClass> weakPtr = sharedPtr;

    std::thread t1(worker, weakPtr);
    std::thread t2(worker, weakPtr);

    t1.join();
    t2.join();

    return 0;
}

6. 初始化问题

  • 原因std::weak_ptr 必须通过 std::shared_ptr 或者另一个 std::weak_ptr 来初始化,不能直接指向一个原始指针。
  • 示例代码
arduino 复制代码
#include <memory>

class MyClass {};

int main() {
    MyClass* rawPtr = new MyClass();
    // 错误:不能直接用原始指针初始化 std::weak_ptr
    // std::weak_ptr<MyClass> weakPtr(rawPtr); 

    std::shared_ptr<MyClass> sharedPtr(rawPtr);
    std::weak_ptr<MyClass> weakPtr(sharedPtr); // 正确的初始化方式
    return 0;
}

通过注意以上这些方面,可以更安全、高效地使用 std::weak_ptr

相关推荐
UpUpUp……19 分钟前
C++继承与组合完结
开发语言·c++·笔记
C++ 老炮儿的技术栈3 小时前
设计模式,如单例模式、观察者模式在什么场景下使用
c++·笔记·学习·算法
Vitalia7 小时前
⭐算法OJ⭐二叉树的后序遍历【树的遍历】(C++实现)Binary Tree Postorder Traversal
开发语言·c++·算法·二叉树
飞鼠_8 小时前
c++简单实现redis
c++·redis·bootstrap
二进制人工智能8 小时前
【QT5 多线程示例】互斥锁
开发语言·c++·qt
沈阳信息学奥赛培训8 小时前
C++语法之命名空间二
开发语言·c++·算法
左直拳10 小时前
C++程序从windows移植到linux后cmake脚本CMakeLists.txt的修改
linux·c++·windows·cmake·cmakelists·移植到linux
JKHaaa11 小时前
判断是不是完全二叉树(C++)
数据结构·c++·算法
程序员yt12 小时前
自动化大一C/C++学完语法如何准备实习?想上手项目却无从下手计算机四大件必须学吗?
c语言·c++·自动化
Dream it possible!13 小时前
LeetCode 热题 100_跳跃游戏 II(79_45_中等_C++)(贪心算法)
c++·算法·leetcode·贪心算法