【C++11】weak_ptr智能指针使用详解

12 篇文章 1 订阅

系列文章目录

【C++11】智能指针与动态内存



简介

在C++编程中,处理循环引用是一个常见的问题。循环引用可能导致内存泄漏和资源管理问题。为了解决这个问题,C++11引入了weak_ptr智能指针。

弱指针(weak_ptr)是一种不受控制所指向对象生存期的智能指针,它指向由一个shared_ptr管理的对象。允许你共享对象的所有权,但不会增加对象的引用计数。它是一种弱引用,不会阻止对象的销毁。一旦一个指向对象的shared_ptr被销毁,对象就会被释放。即使有weak_ptr指向对象,对象也会被释放。


一、头文件

本文出现的关于unique_ptr的方法都包含在此头文件中

#include <memory>

二、初始化及使用

1. 使用一个shared_ptr来初始化

用一个shared_ptr来初始化,不会改变shared_ptr的引用计数

auto p = std::make_shared<int>(4);   
std::weak_ptr<int> wp(p);  

由于wp指向的对象可能不存在,我们不能使用weak_ptr直接访问对象,而必须调用lock。

此函数检查weak_ptr指向的对象是否存在。如果存在,lock返回一个指针指向共享对象的shared_ptr;否则返回一个空的shared_ptr。

if (std::shared_ptr<int> np = wp.lock())  // 如果np不为空则条件成立
{
	// np与p共享对象
}

三、循环引用

3.1 循环引用

环引用是指两个或多个智能指针相互引用,形成一个闭环,导致它们都无法被释放,从而造成内存泄漏。循环引用通常发生在两个或多个对象之间存在相互依赖关系时,它们需要同时被释放。

下述代码就是循环引用的典型例子,a和b所指向的对象的引用计数永远不会为0,也就不会释放对象。

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

class B
{
public:
	std::shared_ptr<A> a_ptr;  //这里改成weak_ptr即可解决循环引用
	B() {
		std::cout << "B Constructor Called" << std::endl;
	};
	~B() {
		std::cout << "B Desstructor Called" << std::endl;
	};
};

int main()
{ 
	std::shared_ptr<A> a = std::make_shared<A>();
	std::shared_ptr<B> b = std::make_shared<B>();
	
	std::cout << "a uses" << " " << a.use_count() << std::endl;   // 引用计数输出1
	std::cout << "b uses" << " " << b.use_count() << std::endl;   // 引用计数输出1
	
	a->b_ptr = b;
	b->a_ptr = a;
	
	std::cout << "a uses" << " " << a.use_count() << std::endl;   // 引用计数输出2
	std::cout << "b uses" << " " << b.use_count() << std::endl;   // 引用计数输出2

std::cout << "Hello World!\n";
}

析构规定

在C++中,局部变量的析构顺序与它们的构造顺序相反。也就是说,最后构造的对象会首先被析构。这被称为栈解旋(stack unwinding)。

在C++中,当一个对象被销毁,其成员变量的析构函数会在该对象的析构函数之后被调用。也就是说,首先调用对象的析构函数,然后调用其成员变量的析构函数。确保了在对象的析构函数中,你仍然可以访问和操作其成员变量,因为它们此时还没有被销毁。

shared_ptr的析构函数会递减它所指向的对象的引用计数。如果引用计数变为0,shared_ptr的析构函数就会销毁对象,并释放它所占用的内存。

上述代码执行完毕,析构函数的调用情况以及引用计数变化情况的说明

①a 和 b 离开作用域之前

a和b->a_ptr指向A的同一个实例对象,该对象的引用计数为2
b和a->b_ptr指向B的同一个实例对象,该对象的引用计数为2

②b调用析构函数

a和b离开作用域时,b先调用析构函数,这会导致b指向对象的引用计数减1,但是,由于a->b_ptr指向同一个对象,所以b指向对象的引用计数仍然为1。

③b->a_ptr调用析构函数

因为b指向对象的引用计数不为0,故其其析构函数没有被执行,对象没有被销毁。这导致其成员变量b->a_ptr的析构函数也不会被调用(对象本身没有被销毁的话,其成员变量的析构函数不会被调用),a指向对象的引用计数不变仍为2。

④a调用析构函数

a和b离开作用域时,b先调用析构函数,然后a调用析构函数,这会导致a指向对象的引用计数减1,但是,由于b->a_ptr也指向同一个对象,所以a指向对象的引用计数仍为1。

⑤a->b_ptr调用析构函数

因为a指向对象的引用计数不为0,故其析构函数没有被执行,对象没有被销毁。这导致其成员变量a->b_ptr的析构函数也不会被调用,b指向对象的引用计数不变仍为1。

最后对象a和b的引用计数都为1,不会销毁对象,释放内存,从而导致内存泄漏。

3.2 循环引用 解决方法

将B类中的shared_ptr改为weak_ptr,这样就可以打破循环引用,当a和b离开作用域时,它们的引用计数会变为0,析构函数会被调用,内存会被释放。

①a和b离开作用域之前

a指向A的一个实例对象,a指向对象的引用计数是1(b->a_ptr弱指针,引用计数不会加1)
b和a->b_ptr指向B的同一个实例对象b,对象b引用计数是2

②b调用析构函数

a和b离开作用域时,b先调用析构函数,这会导致b指向对象的引用计数减1,但是,由于a->b_ptr指向同一个对象,所以b指向对象的引用计数仍然为1。

③b->a_ptr调用析构函数

因为b指向对象的引用计数不为0,故其其析构函数没有被执行,对象没有被销毁。这导致其成员变量b->a_ptr的析构函数也不会被调用(对象本身没有被销毁的话,其成员变量的析构函数不会被调用),a指向对象的引用计数不变仍为1。

④a调用析构函数

a和b离开作用域时,b先调用析构函数,然后a调用析构函数,这会导致a指向对象的引用计数减1。但因为b->a_ptr是弱指针,不会增加引用计数,所以a指向对象的引用计数变为0,会执行析构函数,销毁对象,释放内存。

⑤a->b_ptr调用析构函数

因为a指向对象的引用计数为0,故其析构函数被执行,对象被销毁。从而其成员变量a->b_ptr的析构函数也会被调用,b指向对象的引用计数递减也变为0,销毁对象,释放内存。

最后对象a和b的引用计数都为0,不会导致内存泄漏


  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值