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

相关推荐
Unpredictable2221 小时前
【VINS-Mono算法深度解析:边缘化策略、初始化与关键技术】
c++·笔记·算法·ubuntu·计算机视觉
PingdiGuo_guo2 小时前
C++智能指针的知识!
开发语言·c++
Chuncheng's blog2 小时前
CentOS 7如何编译安装升级gcc至7.5版本?
linux·运维·c++·centos
愚润求学4 小时前
【C++】类型转换
开发语言·c++
@我漫长的孤独流浪4 小时前
数据结构测试模拟题(4)
数据结构·c++·算法
csdnzzt4 小时前
从内存角度透视现代C++关键特性
c++
jie188945758665 小时前
C++ 中的 const 知识点详解,c++和c语言区别
java·c语言·c++
明月*清风5 小时前
c++ —— 内存管理
开发语言·c++
西北大程序猿6 小时前
单例模式与锁(死锁)
linux·开发语言·c++·单例模式
qq_454175796 小时前
c++学习-this指针
开发语言·c++·学习