一文详解C++11的三种智能指针

全面理解-c++11中的智能指针 智能指针是现代 C++ 内存管理的核心工具,通过明确所有权和自动资源释放,显著提升代码安全性和可维护性。原则,确保资源在生命周期结束时被正确释放,避免内存泄漏。:所有权转移语义不明确(通过拷贝构造函数转移所有权),易导致悬空指针。包含两个指针:一个指向对象,一个指向控制块(含引用计数和删除器)。不增加引用计数,但能检测资源是否有效。:唯一拥有资源,不可复制,但可通过。,是编写高质量 C++ 代码的关键。)管理资源,多个指针共享所有权。资源生命周期不确定,需自动管理。管理的资源,不参与所有权管理。 阅读详情
AI摘要

本文详细解读了C++11中的三种智能指针——unique_ptr、shared_ptr和weak_ptr,介绍了它们的关系、各自特点以及在内存管理中的作用,包括unique_ptr解决浅拷贝问题、shared_ptr的引用计数避免内存泄漏,以及weak_ptr用于解决交叉引用问题。

一、三种智能指针的关系概述

在C++11中出现三种智能指针,分别是unique_ptr、shared_ptr、weak_ptr。这三种智能指针分别有各自的应用场景,同时这些应用场景是有相互联系的。

总的来说:

1. unique_ptr的出现,在一定程度上解决了裸指针使用完没有及时释放导致的内存泄漏问题,但多个unique_ptr不能指向同一资源;

2. shared_ptr的出现,解决了unique_ptr不能指向同一资源的问题。它的特点是引用计数不为0,就坚决不释放资源。这又导致了另一个问题:交叉引用导致资源无法释放,又会内存泄漏;

3. weak_ptr的特点是引用对象时引用计数不增加,但自能成为资源的观察者,无法使用资源。如果要使用资源,必须升级为shared_ptr。weak_ptr解决了shared_ptr交叉引用的问题。

二、unique_ptr

和名字一样,unique_ptr的特点就是“独占”的意思,多个unique_ptr无法指向同一对象。相比于裸指针,unique_ptr的最大优势当然是超出作用域时会自动释放资源。其实unique_ptr解决的并不只是资源自动释放的问题。在C++11之前还有auto_ptr、scope_ptr这两种类型的指针,unique_ptr在资源自动释放的同时也解决了两个指针的拷贝、赋值问题(指针的浅拷贝问题)。

假设有下面的代码:

#include <iostream>
#include <memory>

int main() {	
	std::auto_ptr<int> p1(new int(100));
	std::auto_ptr<int> p2(p1);  // 拷贝构造
	std::cout << *p1 << std::endl; // 拷贝构造之后还在使用原来的资源
}

在上面的代码中,p2是通过p1的拷贝构造而来的,但这个拷贝构造就很坑了,是指针的浅拷贝。我们可以看一下auto_ptr的源码:

auto_ptr(auto_ptr& _Right) noexcept
		: _Myptr(_Right.release())
		{	// construct by assuming pointer from _Right auto_ptr
		}

_Ty * release() noexcept
		{	// return wrapped pointer and give up ownership
		_Ty * _Tmp = _Myptr;
		_Myptr = nullptr;
		return (_Tmp);
		}

在上面的代码中,_Myptr就是智能指针管理的对象。在拷贝构造的过程中,对象p1先调用了release函数,而release函数是先将p1的管理对象暂时保存起来,然后把它管理的对象指向nullptr,再用前面保存的临时对象去初始化p2的对象。现在好了,p1偷偷摸摸的把自己管理的对象置成了nullptr,可我们并不知道!当我们继续想使用p1的时候程序就会崩溃了!这个过程,用户完全不知情啊,因此,为了解决这个问题,unique_ptr登场了。很明显,由于unique_ptr是“独占一份资源”,如果要把unique_ptr拷贝给另一个unique_ptr,上面的步骤肯定是要发生的,那怎么办呢?unique_ptr的设计者说,我不给拷贝了,我要把我的拷贝构造和拷贝赋值函数都声明成delete的。哈哈,这下没有指针浅拷贝的问题了吧!如下:
在这里插入图片描述
如果想用unique_ptr的拷贝构造或者拷贝赋值函数,编译器直接报错了。那现在我想把一个unique_ptr的资源交给另一个unique_ptr管理,怎么办呢?在auto_ptr中,出先的问题是auto_ptr偷偷摸摸的把我们的资源转移了,而我们不知道。所以unique_ptr的设计者说,这个资源转移的任务,就交给你们自己完成。没错,std::move这个时候发挥作用了:

在这里插入图片描述

如果我们自己来完成资源转移的任务后,还想使用原来的资源,那不就是你的锅?现在把原来用p1的左值去初始化p2变成了p1的右值去初始化p2了。由于我们知道std:move函数之后原对象的资源就不可用了,因此就不会发生拷贝构造之后还想使用原来对象的问题了。同时,可以看见上面我们拷贝时传入一个右值就不报错了,所以肯定是unique_ptr虽然delete了拷贝构造、拷贝赋值,但人家定义了移动构造、移动赋值来进行对象资源的转移。

三、shared_ptr

如果我想多个指针共享一个资源怎么办?所以相比于unique_ptr,shared_ptr登场了。其实 shared_ptr的核心就是引入了一个引用计数,表示现在有多少个对象(shared_ptr)在共享着一份资源(l里面的裸指针),只要还有一个对象在用着这个指针,那就坚决不把这个资源给释放掉。

这里我们实现了一个简易版本的shared_ptr,实现时考虑的结构如下:
在这里插入图片描述

#include <iostream>
#include <memory>

template<typename T>
class CSmartPtr;

template<typename T>
class RefCnt {
public:
	friend CSmartPtr<T>;
	RefCnt(T* ptr = nullptr) : _ptr(ptr), _count(0) {
		if (nullptr != _ptr) {
			++_count;
		}
	}

	// 增加引用计数
	void addRef() {
		++_count;
	}
	// 减少引用计数
	int deleteRef() {
		return --_count;
	}

private:
	T* _ptr;
	int _count;
};


template<typename T>
class CSmartPtr {
public: 
	CSmartPtr(T* ptr = nullptr) : _ptr(ptr) {
		_pRefCnt = new RefCnt<T>(_ptr);
	}

	~CSmartPtr() {
		if (_pRefCnt->deleteRef() == 0) {
			delete _pRefCnt;
			delete _ptr;
		}
	}

	CSmartPtr(const CSmartPtr<T>& src) : _ptr(src._ptr), 
										_pRefCnt(src._pRefCnt) 
	{
		_pRefCnt->addRef();
	}

	CSmartPtr& operator=(const CSmartPtr& rhs) {
		if (this == &rhs) {
			return *this;
		}

		if (_pRefCnt->deleteRef() == 0) {
			delete _ptr;
		}

		_ptr = rhs._ptr;
		_pRefCnt = rhs._pRefCnt;
		_pRefCnt->addRef();

		return *this;
	}

	T& operator*() {
		return *_ptr;
	}
	T* operator->() {
		return _ptr;
	}

	// 返回引用计数
	int use_count() {
		return _pRefCnt->_count;
	}


private:
	T* _ptr;
	RefCnt<T>* _pRefCnt;
};


int main() {
	CSmartPtr<int> p1(new int(100));
	std::cout << *p1 << std::endl;
	std::cout << p1.use_count() << std::endl;

	// 测试拷贝构造
	{
		std::cout << " ========== copy constructor ====== " << std::endl;
		CSmartPtr<int> p2(p1);
		std::cout << *p2 << std::endl;
		std::cout << p1.use_count() << std::endl;
		std::cout << p2.use_count() << std::endl;
	}
	
	// p2出了作用域 引用计数减1
	std::cout << " ========== out of scope ============ " << std::endl;
	std::cout << p1.use_count() << std::endl;

	return 0;
}

tips:注意这里我们没有考虑线程的安全性,其实在标准库中的shared_ptr,里面的计数量count是原子性的,也就是线程安全的。

四、weak_ptr

如果自己按上面的代码实现了一下CSmartPtr,应该对shared_ptr什么时候释放资源很清楚了。只有当shared_ptr的引用计数为0了,才进行资源的释放。但是这又导致了交叉引用的问题。比如有如下代码:

class B;

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

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

int main() {
	std::shared_ptr<A> pA(new A);
	std::shared_ptr<B> pB(new B);

	std::cout << pA.use_count() << std::endl;
	std::cout << pB.use_count() << std::endl;
}

在上面的代码中,智能指针A的里面有个对象是智能指针B类型,而智能指针B的里面有个对象是智能指针A类型。此时没有交叉引用的运行结果,一切正常:

在这里插入图片描述
而出现交叉引用之后:

int main() {
	std::shared_ptr<A> pA(new A);
	std::shared_ptr<B> pB(new B);

	pA->_sptrB = pB;
	pB->_sptrA = pA;

	std::cout << pA.use_count() << std::endl;
	std::cout << pB.use_count() << std::endl;
}

在这里插入图片描述
我们发现,pA和pB的析构函数都没有被调用起来,原因是在main函数结束的时候,pA和pB的引用计数都减了1,但不是0,所以无法调用起它们的析构函数。如果类A、B里面有从堆内存中申请的资源,那就发生内存泄漏了。

那怎么办呢?当然是weak_ptr登场了,在上面的例子中,把类A和B里面的对象类型从shared_ptr改成weak_ptr即可:

class B;

class A {
public:
	A() { std::cout << "A()" << std::endl; }
	~A() { std::cout << "~A()" << std::endl; }
	std::weak_ptr<B> _sptrB;
};

class B {
public:
	B() { std::cout << "B()" << std::endl; }
	~B() { std::cout << "~B()" << std::endl; }
	std::weak_ptr<A> _sptrA;
};

int main() {
	std::shared_ptr<A> pA(new A);
	std::shared_ptr<B> pB(new B);

	pA->_sptrB = pB;
	pB->_sptrA = pA;

	std::cout << pA.use_count() << std::endl;
	std::cout << pB.use_count() << std::endl;
	}

运行结果:
在这里插入图片描述
但是,新的问题出现了:weak_ptr只是对象的观察者,不拥有对象本身。从实现上看,weak_ptr没有重载 "operator *" 和 "operator ->",自然无法使用资源了。

在这里插入图片描述

那怎么解决这个问题呢?答案就是我们要对weak_ptr做一个提升,也就是调用lock函数,把资源“锁住,抓在手里”,再来使用资源:

int main() {
	
std::shared_ptr<A> pA(new A);
	std::shared_ptr<B> pB(new B);

	pA->_sptrB = pB;
	pB->_sptrA = pA;

	pB->testB();
	// 资源的提升
	std::shared_ptr<B> pBTemp = pA->_sptrB.lock();
	if (nullptr != pBTemp) {
		pBTemp->testB();
	}
	// 使用完记得reset释放
	pBTemp.reset();
		
	std::cout << pA.use_count() << std::endl;
	std::cout << pB.use_count() << std::endl;
}

这样就可以欢快的使用资源了。这种抓住资源的动作,其实在多线程的环境中非常有用,在使用共享资源的时候,其实共享资源可能已经被释放了,但本线程并不知道。而有了lock函数之后,就可以尝试“抓住资源”,如果返回不是nullptr,那就可以正常的使用资源了。

至此,C++的智能指针,基本讨论结束了。

c++11新特性-智能指针 智能指针RAII(Resource Acquisition Is Initialization),是一种利用对象的生命周期来管理资源的技术。如果我们采用传统的new/delete来申请和释放资源,如果忘记调用delete,或者在调用delete之前程序抛出异常,都会导致内存泄漏问题,如代码1.1,Func函数中的new p2和Div都可能抛异常,导致后面的delete没有执行从而引发内存泄漏,采用智能指针对资源进行管理,能够杜绝这类问题。代码1.1:因异常引发的内存泄漏throw "除0错误"; 阅读详情

相关推荐

c++11之智能指针(详解)

c++11新特性 智能指针

weixin_61503529的博客 4036

【C++11】智能指针

【C++11】智能指针

weixin_60478154的博客 8318

C++11中的智能指针unique_ptr、shared_ptr和weak_ptr详解

详细讲解C++11中引入的智能指针unique_ptr、shared_ptr和weak_ptr。

dvlinker的技术专栏 3万+

C++ 11 -智能指针

什么是内存泄漏:内存泄漏指因为疏忽或错误造成程序未能释放已经不再使用的内存的情况。内存泄漏并不是指内存在物理上的消失,而是应用程序分配某段内存后,因为设计错误,失去了对该段内存的控制,因而造成了内存的浪费。内存泄漏的危害:长期运行的程序出现内存泄漏,影响很大,如操作系统、后台服务等等,出现内存泄漏会导致响应越来越慢,最终卡死。

cat_fish_rain的博客 2703

从硬件到软件:深度解析D435i USB SCP overflow错误的根源与解决方案

本文深入解析Intel RealSense D435i相机在ROS环境下常见的'USB SCP overflow'错误,特别是与`control_transfer`相关的错误提示。从硬件配置到软件优化,详细介绍了如何通过调整USB接口、优化`rs_camera.launch`参数、更新固件驱动等方法解决数据传输溢出问题,提升设备稳定性和性能。

yy01234的博客 771

5分钟搞懂Paris公式:如何用裂纹扩展速率预测零件寿命(附Python代码示例)

本文详细介绍了Paris公式在裂纹扩展速率预测零件寿命中的应用,通过Python代码示例展示了从理论到实践的完整流程。文章涵盖了Paris公式的工程本质、寿命预测计算流程、几何修正因子选择以及实际案例演示,帮助工程师快速掌握这一关键技术。

weixin_29309475的博客 428

C++进阶——C++11_智能指针

什么是内存泄漏:内存泄漏指因为疏忽或错误造成程序未能释放已经不再使用的内存,一般是忘记释放或者发生异常释放程序未能执行导致的。内存泄漏并不是指内存在物理上的消失,而是应用程序分配某段内存后,因为设计错误,失去了对该段内存的控制,因而造成了内存的浪费。内存泄漏的危害普通程序运行一会就结束了出现内存泄漏问题也不大,进程正常结束,页表的映射关系解除,物理内存也可以释放。长期运行的程序出现内存泄漏影响很大。

博客 | 复习知识点 + 练习 + 最终的领悟 | 全力以赴 1525

项目回顾一:《高速多端口共享缓存模块》

本帖子仅为了回顾一下一年前自己动手完成的第一个数字ic设计项目的历程,为复试做一下准备,完成的这个sramc模块花了整整四个月的时间,从0到12个模块的代码产出以及模块的验证,虽然没有拿奖,也没有进入复赛,设计过程中使用的技术手段和算法也不太成熟,但这这是一个值得记录的过程!

weixin_60261760的博客 1256

【C++11】智能指针的使用

C++11智能指针的使用,带你领略智能指针浩瀚的世界

有梦想的电信狗 1282

【完整梳理验证】企业微信第三方应用接入全流程java版

由企业内部的开发者自己开发部署,相当于是企业自己的资产,调用接口基本没有任何限制。由SaaS服务商的开发者开发并部署在服务商侧,面向所有企业。需要企业授权使用(先试用后付费),服务商仅可获取企业授权部分的权限,相当于白名单控制。由服务商的开发者开发,但部署在企业内部。一般是线下签约采购方式,因此权限几乎与自建应用无异,企业管理员只需要配置不对服务商开放的敏感权限,相当于黑名单控制。服务商后台地址:企业微信服务商官网企业管理后台地址:企业微信管理后台服务商,可以理解为 SAAS 中的运营端。企业管理端,可以理

Hello, New World! 2764

WPF进阶 | 深入 WPF 依赖项属性:理解其强大功能与应用场景

WPF进阶 | 深入 WPF 依赖项属性:理解其强大功能与应用场景,在 Windows Presentation Foundation(WPF)开发中,依赖项属性是一个核心且强大的特性,它为构建灵活、可扩展的用户界面提供了关键支持。理解并熟练运用依赖项属性,对于开发高效、健壮的 WPF 应用程序至关重要。本文将深入探讨 WPF 依赖项属性,通过丰富的代码示例和对关键概念的详细解释,帮助读者全面掌握这一重要特性及其应用场景。

不踩坑,不知道坑有多深;不总结,踩过的坑白踩。 4万+

C++11智能指针详解

本文系统介绍了C++11引入的三种智能指针工具:unique_ptr、shared_ptr和weak_ptr,它们基于RAII机制实现了自动内存管理。unique_ptr实现独占所有权,轻量高效;shared_ptr支持共享所有权,具备线程安全的引用计数;weak_ptr专门解决shared_ptr的循环引用问题。文章还分析了废弃的auto_ptr的缺陷,比较了各智能指针的特点和使用场景,并提供了模拟实现代码。

My__Space的博客 1483

华为OD面试八股真题 - 操作系统 - 1

华为OD技术面,操作系统相关八股真题提供详细解答,这篇博客主要涉及于进程和线程相关知识点

qq_45776114的博客 1644

【c++】c++11 的三种智能指针

具体来说,当两个对象相互引用对方的 std::shared_ptr 时,会形成一个循环,导致它们的引用计数无法降为零,进而无法释放内存,此时,Person 和 Car 对象的引用计数都为 1,原因是它们相互持有对方的 std::shared_ptr。,c++11 中,引入了三种智能指针:unique_ptr、shared_ptr 和 weak_ptr,用于自动管理动态分配的内存,防止内存泄漏等问题。person 被销毁,其引用的 Person 对象的引用计数减少 1,变为 1。可以指向同一个对象。

2201_75930743的博客 1146

泛微系统流程表单获取节点操作者

泛微系统流程表单获取节点操作者 select typename 流程类型, workflowname 流程名称, nodename 节点名称, (case when nodetype=0 then '创建' when nodetype=1 then '审批' when nodetype=2 then '提交' when nodetype=3 then '归档' else '' end) as 节点类型, groupname 节点操作组, (case when type=1 then '部门+安

烧硬盘 4616

YOLO26改进策略【卷积层】| 引入注意力卷积模块RFAConv,关注感受野空间特征 助力YOLO26精度提升

RFAConv:创新空间注意力和标准卷积运算感受野空间特征是针对卷积核设计的,根据卷积核大小动态生成。以3×3卷积核为例,它由非重叠滑动窗口组成,每个窗口代表一个感受野滑块。

Limiiiing的博客 194

C++11及C++14标准的智能指针

智能指针这个概念经常会碰见,而且面试的时候太经常会被问到了,特来总结一下。 C++11的智能指针及其背景 auto_ptr的坑点 C++11之前的智能指针是auto_ptr,一开始它的出现是为了解决指针没有释放导致的内存泄漏。比如忘了释放或者在释放之前,程序throw出错误,导致没有释放。所以auto_ptr在这个对象声明周期结束之后,自动调用其析构函数释放掉内存。 int t =

haolexiao的专栏 1万+

假人有限元模型

假人模型,车辆碰撞假人有限元模型,适合做乘员安全的研究者利用

C++11 智能指针:从原理到实现

本文深入探讨了C++11智能指针的实现原理与使用方式。智能指针通过RAII技术自动管理内存资源,有效解决了传统C++手动内存管理的诸多问题。文章重点分析了三种智能指针:unique_ptr实现独占所有权,禁止拷贝但支持移动语义;shared_ptr通过引用计数实现共享所有权;weak_ptr作为弱引用可解决循环引用问题。每种智能指针都配有详细代码示例,展示了创建、操作和资源管理的关键用法。最后还通过简化实现揭示了unique_ptr和shared_ptr的内部工作机制,包括引用计数和自定义删除器的实现细节。

2301_80541270的博客 1445

Data-Structures-and-Algorithm-Analysis-in-Java.pdf

作者: Weiss, Mark A. 出版社: Addison Wesley Data Structures and Algorithm Analysis in Java is an “advanced algorithms” book that fits between traditional CS2 and Algorithms Analysis courses. In the old ACM Curriculum Guidelines, this course was known as CS7. This text is for readers who want to learn good programming and algorithm analysis skills simultaneously so that they can develop such programs with the maximum amo...

Devexpress16.2.6破解工具

Devexpress16.2.6破解工具Devexpress16.2.6破解工具Devexpress16.2.6破解工具Devexpress16.2.6破解工具

c++11之智能指针

智能指针是存储指向动态分配(堆)对象指针的类,用于生存期的控制,能够确保在离开指针所在作用域时,自动地销毁动态分配的对象,防止内存泄露。智能指针的核心实现技术是引用计数,每使用它一次,内部引用计数加1,每析构一次内部的引用计数减1,减为0时,删除所指向的堆内存。C++中用的最多的是下面三种智能指针C++11中提供了三种智能指针,使用这些智能指针时需要引用头文件std::shared_ptr:共享的智能指针std::unique_ptr:独占的智能指针。

weixin_48067074的博客 1268

计算机控制技术及应用

《计算机控制技术及应用》通过总结作者的科研成果和吸收国内外的先进理论、方法和技术,以计算机控制系统的网络化、开放化、智能化和集成化发展趋势为主线索,主要介绍计算机控制系统的基本概念和计算机控制系统的典型形式,计算机控制系统的数学描述与基本分析方法,以及数字PID控制器、最少拍控制系统、达林算法、状态变量设计法等设计方法及其各种改进算法。重点突出计算机控制系统输入输出通道接口的设计方法和典型控制系统的设计案例与实现方法等。

C++ 11智能指针详解和使用

C++11中的智能指针的初步学习

记录一些平时工作或者学习中的瞬间 1820

【C++11】智能指针详解

为何需要智能指针 | C++的智能指针 | C++11和boost中智能指针的关系详解

枫叶先生的博客 1802

C++11智能指针

学完了继承多态,二叉搜索树,map和set,AVL树,红黑树,红黑树的封装,哈希表的底层,C++11新特性,异常,现在我们也迎来了C++的学习的一个尾声智能指针。目录1.写在前面2.智能指针3.C++标准库智能指针的应用4.智能指针的原理shared_ptr实现代码。

2401_87151064的博客 1715

C++11 智能指针详解

C++11智能指针详解:高效安全的内存管理方案 摘要:C++11引入的智能指针通过RAII机制实现了自动内存管理,解决了传统指针的内存泄漏等问题。主要包括三种类型:1) unique_ptr提供独占所有权,禁止复制但支持移动语义,性能最优;2) shared_ptr采用引用计数实现共享所有权,允许多个指针指向同一对象;3) weak_ptr作为shared_ptr的辅助,解决循环引用问题。智能指针通过将资源生命周期与对象绑定,在析构时自动释放内存,显著提高了代码安全性和可维护性。文章详细介绍了各类智能指针的

保持分享欲 1743
上一篇: 一致性哈希
下一篇: 网络编程:阻塞、非阻塞、同步、异步的区分
zsiming
博客等级 码龄7年 43粉丝 74原创
评论 1
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

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

抵扣说明:

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

余额充值