weak_ptr 智能指针的使用

文章详细介绍了C++智能指针中的weak_ptr,包括其构造方法、expired()用于检测对象是否过期、lock()获取shared_ptr以及use_count()检查引用计数。weak_ptr主要解决shared_ptr的循环引用问题,并允许在不延长对象生命周期的情况下检测其有效性。

目录

一,weak_ptr 变量的定义

二,expired() 成员函数

三,lock() 成员函数

四,use_count() 成员函数

五,为什么要用 weak_ptr


一,weak_ptr 变量的定义

weak_ptr 对象的构造有3种方法:

1,构造空对象,如 std::weak_ptr<CTest> weakPtr;

2,拷贝构造,如 std::weak_ptr<CTest> weakPtr2(weakPtr);

3,用shared_ptr 对象进行初始化,如

std::shared_ptr<CTest> ptr = std::make_shared<CTest>();

std::weak_ptr<CTest> weakPtr3(ptr);

#include <stdio.h>
#include <stdlib.h>
#include <memory>

class CTest
{
public:
    CTest(): mValue(100)
    {
        printf("constructor\n");
    }

    ~CTest() 
    {
        printf("destructor\n");
    }
    
    int getValue()
    {
        return mValue;
    }
private:
    int mValue;
};

int main()
{
    std::weak_ptr<CTest> weakPtr;
    std::weak_ptr<CTest> weakPtr2(weakPtr);
	std::shared_ptr<CTest> ptr = std::make_shared<CTest>();
    std::weak_ptr<CTest> weakPtr3(ptr);

    int value = ptr->getValue();
    printf("value = %d, use_count = %d\n", value, ptr.use_count());

    value = (weakPtr3.lock())->getValue();
    printf("value = %d\n", value);

    ptr.reset();
    auto ptr2 = weakPtr3.lock();
    if(ptr2 == nullptr)
    {
        printf("weakPtr3.lock() = nullptr\n");
        return 0;
    }
    value = ptr2->getValue();
    return 0;
}

二,expired() 成员函数

bool expired() const noexcept; 函数返回 weak_ptr 对象是否是空的,或是它所属的所有者组中不再有shared_ptr。此函数与 use_count() == 0 意义相同。

#include <stdio.h>
#include <stdlib.h>
#include <memory>

class CTest
{
public:
CTest(): mValue(100)
{
	printf("constructor\n");
}

~CTest() 
{
	printf("destructor\n");
}

int getValue()
{
	return mValue;
}
private:
	int mValue;
};

int main()
{
	std::weak_ptr<CTest> weakPtr;
	std::weak_ptr<CTest> weakPtr2(weakPtr);
	std::shared_ptr<CTest> ptr = std::make_shared<CTest>();
	std::weak_ptr<CTest> weakPtr3(ptr);

	if(weakPtr.expired())
	{
		printf("weakPtr is empty!\n");
	}

	if(weakPtr2.expired())
	{
		printf("weakPtr2 is empty!\n");
	}

	if(weakPtr3.expired())
	{
		printf("weakPtr3 is empty!\n");
	}

	return 0;
}

我们可以看到源码是这样的:

三,lock() 成员函数

shared_ptr<element_type> lock() const noexcept; 函数返回一个shared_ptr,其中包含weak_ptr对象在未过期时保留的信息。如果 weak_ptr 对象已过期(包括它是否为空),该函数将返回一个空的shared_ptr(就像默认构造的一样)。

#include <stdio.h>
#include <stdlib.h>
#include <memory>

class CTest
{
public:
    CTest(): mValue(100)
    {
        printf("constructor\n");
    }

    ~CTest() 
    {
        printf("destructor\n");
    }
    
    int getValue()
    {
        return mValue;
    }
private:
    int mValue;
};

int main()
{
    std::weak_ptr<CTest> weakPtr;
    std::weak_ptr<CTest> weakPtr2(weakPtr);
    std::shared_ptr<CTest> ptr = std::make_shared<CTest>();
    std::weak_ptr<CTest> weakPtr3(ptr);

	std::shared_ptr<CTest> lck;
	lck = weakPtr.lock();
	if(lck == nullptr)
	{
		printf("weakPtr is empty!\n");
	}

	if(!weakPtr2.lock())
	{
		printf("weakPtr2 is empty!\n");
	}

	std::shared_ptr<CTest> test;
	if((test = weakPtr3.lock()) != nullptr)
	{
		printf("weakPtr3 is not empty! value = %d\n", test->getValue());

	}

    return 0;
}

在使用 lock() 函数时,可以判断其是否为nullptr。当用 gdb 查看时可以看到:lck=weakPtr.lock()后,lck 就是 0。 

我们可以看源码 lock() 函数是怎样实现的:

可以看到当对象过期时,构造了一个空的 shared_ptr<>对象进行返回,而这个空对象它的初始化值是这样的:

_M_ptr 是模板参数类型的指针,它指向的就是要管理的对象。而 _M_refcount  是管理引用计数的。所以一个空的 shared_ptr<> 并不管理任何对象,就不能当作对象指针来用了。但 use_count() 还是可以用的,用 lck.use_count() == 0 进行判断:

 

这里可能会有人疑问:lck 都是nullptr 了,怎么还能调用 use_count() 呢?这样调用不会崩溃吗?这个就要理解类与对象的关系了,可以参考 null 类对象。可以看源码:

四,use_count() 成员函数

weak_ptr 里有两个计数,一个是 _M_use_count 管理对象的引用计数,一个是 _M_weak_count 是不是用于它本身的析构用的,还没研究。weak_ptr 的基类:

      _Tp*	 	 _M_ptr;         // Contained pointer.
      __weak_count<_Lp>  _M_refcount;    // Reference counter.
  template<_Lock_policy _Lp>
    class __weak_count
    {
    public:
      constexpr __weak_count() noexcept : _M_pi(0)
      { }

      __weak_count(const __shared_count<_Lp>& __r) noexcept
      : _M_pi(__r._M_pi)
      {
	if (_M_pi != 0)
	  _M_pi->_M_weak_add_ref();
      }

 __weak_count<_Lp> 模板类有一个成员 _M_pi, 其类型为 _Sp_counted_base<_Lp>*  _M_pi;

 _Sp_counted_base<_Lp> 就包含上述的两个计数。use_count() 函数调用的是_M_refcount._M_get_use_count(); 

五,为什么要用 weak_ptr

1,解决shared_ptr 循环引用的问题。这个网上也有很多例子

2,在不延长shared_ptr 管理对象生命周期的情况下,探知这个对象是否已经无效。可以参考 muduo 的例子​​​​​​​

C++11 引入的 `std::weak_ptr` 是一种非拥有型智能指针,与 `std::shared_ptr` 配合使用,用于解决共享所有权场景下的循环引用问题,本身不拥有对象的所有权,仅提供对 `shared_ptr` 管理对象的临时访问能力,不会影响对象的生命周期 [^2]。 ### 引入背景 `shared_ptr` 通过引用计数解决了指针独占的问题,但会产生引用成环问题,导致内存泄漏。`weak_ptr` 就是为弥补 `shared_ptr` 这一缺陷而引入标准库的 [^1]。 ### 核心优势 1. **打破循环引用**:解决 `shared_ptr` 形成的循环引用导致的内存泄漏 [^2]。 2. **非侵入式观察**:不增加引用计数,不影响对象的销毁时机 [^2]。 3. **安全访问**:提供 `lock()` 方法安全获取对象访问权,避免悬垂指针 [^2]。 4. **轻量级设计**:仅需存储控制块指针,开销小 [^2]。 ### 创建方式 - **创建空的 `weak_ptr`**:创建一个不指向任何对象的 `weak_ptr` [^3]。 - **拷贝构造函数创建 `weak_ptr`**:用一个已有的 `weak_ptr` 来创建新的 `weak_ptr` [^3]。 - **利用已有的 `shared_ptr` 指针创建 `weak_ptr`**:将 `weak_ptr` 绑定到一个 `shared_ptr` 上,不会改变 `shared_ptr` 的引用计数 [^3][^4]。 ### 成员方法 `weak_ptr` 提供了一些成员方法,其中 `lock()` 方法较为重要,它可以安全地获取对象访问权。当调用 `lock()` 时,如果 `weak_ptr` 所指向的对象仍然存在(即对应的 `shared_ptr` 还在管理该对象),则返回一个指向该对象的 `shared_ptr`;如果对象已经被销毁,则返回一个空的 `shared_ptr`,从而避免了悬垂指针的问题 [^2]。 ### 示例代码 ```cpp #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; // 使用 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; } ``` 在上述代码中,`class B` 中的 `a_ptr` 使用了 `std::weak_ptr`,避免了 `A` 和 `B` 之间的循环引用。当 `main` 函数结束时,`a` 和 `b` 的引用计数减为 0,`A` 和 `B` 对象会被正确销毁。
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值