c++11新特性-智能指针

news2024/9/20 0:41:10

1. 智能指针的概念及原理


1.1 什么是智能指针

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

代码1.1:因异常引发的内存泄漏

int Div(int a, int b)
{
	if (b == 0) {
        throw "除0错误";
    } else {
        return a / b;
    }
}
 
void Func()
{
	int* p1 = new int[5];
	int* p2 = new int[5];
 
	// 这里Div函数会抛异常,main函数会捕获异常,delete[]没有执行,引发内存泄漏
	int ret = Div(5, 0);
 
	delete[] p1;
	delete[] p2;
}
 
int main()
{
	try {
		Func();
	} catch (std::exception& e) {
		std::cout << e.what() << std::endl;
	} catch (...) {
		std::cout << "未知错误" << std::endl;
	}
 
	return 0;
}

智能指针是一个类,在对象构造时调用构造函数获取资源,在对象生命周期内,保证资源不被释放,在对象生命周期结束时,编译器自动调用析构函数来释放资源。这就相当于,将管理资源的责任移交给了对象,这样即使程序抛出异常也不存在内存泄漏,因为捕获异常往往跳出函数体,执行流会离开对象的作用域,对象生命周期结束,编译器自动调用析构函数释放了资源。

采用智能指针管理资源,有如下优点:
1> 将资源管理的责任转移给智能指针对象,不用显示地释放资源,杜绝了异常安全问题。
2> 保证对象管理的资源在其生命周期内有效。
代码1.2定义了一种简易的智能指针SmartPtr,在其析构函数中会对资源进行释放。因为申请的资源可能是通过new T、new T[n]、malloc(…)这几种方法的任意之一来申请的,每种方式申请的资源需要不同的关键字/函数来释放资源,否则程序可能会崩溃。

因此,需要一个模板参数Del,这个模板参数用于接收仿函数,以匹配不同的资源释放方式。我们默认采用delete的方式释放资源,C++标准库中提供的智能指针,如果不显示给定仿函数来定义释放资源的方式,也是默认delete释放资源。

代码1.2:智能指针的简易实现 – SmartPtr

template<class T>
struct Delete
{
	void operator()(T* ptr) { delete ptr;}
};
 
template<class T>
struct DeleteArray
{
	void operator()(T* ptr) { delete[] ptr; }
};
 
template<class T>
struct Free
{
	void operator()(T* ptr) { free(ptr); }
};
 
template<class T, class Del = Delete<T>>
class SmartPtr
{
public:
	SmartPtr(T* ptr = nullptr)  //构造函数
		: _ptr(ptr)
	{ }
 
	~SmartPtr()
	{
		Del del;
		del(_ptr);
	}
 
private:
	T* _ptr;  //管理的资源
};
1.2 智能指针的原理

智能指针,顾名思义,不能仅仅是用于资源管理,还应当具有指针的一般功能。因此,需要重载operator*、operator->函数(见代码1.3),用于访问指针指向的资源。注意:C++标准库中的智能指针均不重载operator[]函数。

智能指针原理总结:

RAII,管理资源,对象创建时申请资源,对象生命周期结束时释放资源。
具有像指针一样的行为:operator*、operator->。
代码1.3:SmartPrt

template<class T, class Del = Delete<T>>
class SmartPtr
{
public:
	SmartPtr(T* ptr = nullptr)  //构造函数
		: _ptr(ptr)
	{ }
 
	~SmartPtr()
	{
		Del del;
		del(_ptr);
	}
 
	T& operator*()
	{
		return *_ptr;
	}
 
	T* operator->()
	{
		return _ptr;
	}
 
private:
	T* _ptr;  //管理的资源
};

2. 智能指针的拷贝问题


我们要求智能指针具有一般的指针行为,因此,我们也就需要智能指针支持拷贝。但是,智能指针中的成员涉及到执行动态申请资源的指针,按照一般要求,应当进行深拷贝。

但是如果我们进行深拷贝,就会让两个智能指针指向不同的空间,但是我们所希望的是两个指针共同管理一块资源,因此我们就是要浅拷贝(值拷贝)。

但是值拷贝会存在对同一块空间多次释放的问题,对此,C++标准库中的智能指针auto_ptr和shared_ptr分别采用了管理权转移和引用计数的方法来解决问题,但一般会通过引用计数解决多次释放的问题(见第3、4章)。

智能指针拷贝问题总结:

即使涉及到动态申请内存,智能指针的拷贝也不应为深拷贝,应当是浅拷贝。
采用管理权转移(auto_ptr)或引用计数(shared_ptr)来解决同一块空间多次释放的问题。
一般都使用引用计数来解决多次释放问题,auto_ptr大部分情况下不使用。
智能指针的拷贝构造原理如图所示:
在这里插入图片描述

3. auto_ptr


3.1 auto_ptr的拷贝构造和赋值问题

auto_ptr采用管理权转移的方法进行赋值和拷贝构造,假设原先有一个auto_ptr对象p1,要通过p1构造p2,当拷贝构造完成后,用于拷贝构造传参的对象p1中管理资源的指针会被更改为nullptr,赋值也一样,假设p2=p1,p1中资源的管理权会转移给p2,p2原本的资源会被释放。

采用管理权转移的方法进行智能指针拷贝是一种极不负责任的行为,auto_ptr已经被很多公司明令禁止使用,一般项目中也极少使用auto_ptr。
auto_ptr的拷贝构造如图所示:
在这里插入图片描述
auto_ptr的复制如图所示:
在这里插入图片描述

3.2 auto_ptr的模拟实现

这里最需要注意的问题是实现operator=函数时的自赋值检验,因为p2=p1要涉及到资源释放,如果p1和p2指向同一块资源,p2的资源被先行释放,那么p2=p1后,p2依旧指向原来的空间,但这块空间的使用权利已经换给了操作系统。

template<class T>
class auto_ptr
{
public:
    //构造函数
    auto_ptr(T* ptr = nullptr)
        : _ptr(ptr)
    { }

    //拷贝构造函数
    auto_ptr(auto_ptr<T>& ap)
        : _ptr(ap._ptr)
    {
        ap._ptr = nullptr;   //管理权转移
    }

    //赋值函数
    auto_ptr<T>& operator=(auto_ptr<T>& ap)
    {
        if (_ptr != ap._ptr)  //自赋值检验
        {
            delete _ptr;
            _ptr = ap._ptr;
            ap._ptr = nullptr;
        }

        return *this;
    }

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

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

    //析构函数
    ~auto_ptr()
    {
        delete _ptr;
    }

private:
    T* _ptr;
};

4. unique_ptr


unique直接将拷贝构造和赋值禁止,也就不存在浅拷贝的多次释放同一块空间的问题。

template<class T>
class unique_ptr
{
public:
    //构造函数
    unique_ptr(T* ptr = nullptr)
        : _ptr(ptr)
    { }

    //使用delete关键字强行禁止拷贝构造函数和赋值函数的生成
    unique_ptr(const unique_ptr<T>& up) = delete;
    unique_ptr<T>& operator=(const unique_ptr<T>& up) = delete;

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

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

    //析构函数
    ~unique_ptr()
    {
        delete _ptr;
    }

private:
    T* _ptr;
};

5. shared_ptr


shared是C++11标准库中新纳入的智能指针,它通过引用计数的方法,较好的解决了拷贝和赋值的问题,是实际项目中最常用的智能指针。

5.1 shared_ptr的常用接口

在这里插入图片描述

5.2 shared_ptr的拷贝构造和赋值问题

shared内部有一个成员变量long int* _pcount,它指向一块存储引用计数的空间,当进行拷贝构造时,引用计数+1,即:++(_pcount)。
进行赋值操作(sp2 = sp1)时,首先应当检查自赋值,如果是自赋值直接返回
this即可。如果不是自赋值,那么首先将sp2的引用计数-1,如果sp2的引用计数-1后变为了0,那么就释放sp2的资源,然后赋予sp2管理sp1管理的资源的权限,sp2和sp1共用一个引用计数,引用计数+1。
调用析构函数时,先让引用计数-1,如果此时引用计数变为0,就释放资源。
shared_ptr拷贝构造如图所示:
在这里插入图片描述
shared_prt赋值如图所示:
在这里插入图片描述

5.3 shared_ptr的模拟实现
template<class T, class Del = Delete<T>>
class shared_ptr
{
public:
    //构造函数
    shared_ptr(T* ptr = nullptr)
        : _ptr(ptr)
        , _pcount(new long int(1))
    { }

    //拷贝构造函数
    shared_ptr(shared_ptr<T>& sp)
        : _ptr(sp._ptr)
        , _pcount(sp._pcount)
    {
        ++(*_pcount);  //引用计数+1
    }

    //赋值函数
    shared_ptr<T>& operator=(shared_ptr<T>& sp)
    {
        if (_ptr == sp._ptr)  //自赋值检查
        {
            return *this;
        }

        //this的引用计数-1,并判断是否需要释放资源
        if (--(*_pcount) == 0)
        {
            Del del;
            del(_ptr);
            delete _pcount;
        }

        _ptr = sp._ptr;
        _pcount = sp._pcount;
        ++(*_pcount);

        return *this;
    }

    //指针获取函数
    T* get()
    {
        return _ptr;
    }

    //引用计数获取函数
    long int use_count()
    {
        return *_pcount;
    }

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

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

    bool unique()
    {
        return *_pcount == 1;
    }

    //析构函数
    ~shared_ptr()
    {
        if (--(*_pcount) == 0)
        {
            Del del;
            del(_ptr);
            delete _pcount;
        }
    }

private:
    T* _ptr;   //指向动态申请空间的指针
    long int* _pcount;   //引用计数
};
5.4 shared_ptr的循环引用问题

在绝大部分情况下,shared_ptr能够解决智能指针赋值造成的多次析构问题,也不会引发内存泄漏。但是,代码4.1展现了一种特殊情况,定义一个Node节点,其中包含两个shared_ptr成员_prev和_next。在主函数中实例化出两个shared_ptr对象n1和n2,n1的_next指向n2,n2的_prev指向n1,n1和n2相互指向对方,这样就属于循环引用,会造成n1和n2的资源释放失败,引发内存泄漏问题。

代码4.1:循环引用

struct Node
{
	int _val;
	std::shared_ptr<Node> _prev;
	std::shared_ptr<Node> _next;
 
	~Node()
	{
		std::cout << "~Node()" << std::endl;
	}
};
 
int main()
{
	std::shared_ptr<Node> n1(new Node);
	std::shared_ptr<Node> n2(new Node);
 
	n1->_next = n2;
	n2->_prev = n1;
 
	return 0;
}

在代码4.1中,循环引用的成因如下:

构造对象n1和n2,引用计数为1,然后n1->_next = n2、n2->_prev = n1后,引用计数变为2。
先后由n2和n1调用析构函数,引用计数变为1。
此时,n1和n2的资源还都没有释放,n1的_next依旧指向n2,n2的_prev依旧指向n1。
n1释放,就需要n2的_prev成员释放,n1释放,就需要n1的_next成员释放。但是,只有对象本身析构,它的成员才会析构,因此n1和n2彼此制约对方的析构,最终n1和n2的资源都无法释放,造成了内存泄漏。
在这里插入图片描述
为了避免循环引用,可以把Node节点中的_next和_prev成员变量的类型改为weak_ptr,weak_ptr是C++标准库中的比较特殊的一个“智能指针”,允许使用shared_ptr对象来构造weak_ptr对象,但是,weak_ptr不增加引用计数,不参与资源的申请和释放,从严格意义上讲,weak_ptr不算是智能指针。

代码4.2:使用weak_ptr避免引用计数

struct Node
{
	int _val;
	std::weak_ptr<Node> _prev;
	std::weak_ptr<Node> _next;
 
	~Node()
	{
		std::cout << "~Node()" << std::endl;
	}
};
 
int main()
{
	std::shared_ptr<Node> n1(new Node);
	std::shared_ptr<Node> n2(new Node);
 
	n1->_next = n2;
	n2->_prev = n1;
 
	return 0;
}

6. weak_ptr


weak_ptr不参与资源的管理和释放,可以使用shared_ptr对象来构造weak_ptr对象,但是不能直接使用指针来构造weak_ptr对象,在weak_ptr中,也没有operator*函数和operator->成员函数,不具有一般指针的行为,因此,weak_ptr严格意义上并不是智能指针,weak_ptr的出现,就是为了解决shared_ptr的循环引用问题。

weak_ptr在进行拷贝构造和赋值时,不增加引用计数,由于weak_ptr不参与资源管理,也不需要显示定义析构函数来释放资源。

template<class T>
class weak_ptr
{
public:
	//默认构造函数
	weak_ptr()
		: _ptr(nullptr)
	{ }
 
	//拷贝构造函数
	weak_ptr(weak_ptr<T>& wp)
		: _ptr(wp._ptr)
	{ }
 
	//采用shared_ptr构造
	weak_ptr(shared_ptr<T>& sp)
		: _ptr(sp.get())
	{ }
 
	//赋值函数
	weak_ptr<T>& operator=(weak_ptr<T>& wp)
	{
		_ptr = wp._ptr;
	}
 
	//通过shared_ptr对象赋值
	weak_ptr<T>& operator=(shared_ptr<T>& sp)
	{
		_ptr = sp.get();
	}
 
private:
	T* _ptr;
};

7. 总结


1> 智能指针是用来对资源进行管理的类,在对象创建时动态申请内存资源,在对象生命周期结束时由编译器自动调用析构函数完成资源的释放,智能指针除了用来管理资源外,还应当具有指针的一般行为(operator函数和operator->函数)。
2> 使用智能指针,相当于把资源管理的责任转交给智能指针对象。这样能够有效避免因为忘记delete或者程序抛出异常而引起的内存泄漏。
3> 智能指针应支持浅拷贝,但是浅拷贝存在同一块空间被多次释放的问题,为此,C++标准库中的三种智能指针auto_ptr、unique_ptr和shared_ptr分别采用了不同的方法来解决这一问题。
auto_ptr支持拷贝的方式是进行管理权转移,这是一种不负责任的处理方式,auto_ptr因此被许多公司禁止使用。
4> unique_ptr直接强行禁止拷贝构造和赋值。
5> shared_ptr通过引用计数的方式来进行浅拷贝,当引用计数为0时析构函数才释放资源,这样既支持了浅拷贝,也保证一块空间仅被析构一次。但是shared_ptr存在循环引用这一隐患,会造成内存泄漏。
6> 使用weak_ptr可以避免shared_ptr的循环引用问题,weak_ptr可以通shared_ptr对象来构造而不增加引用计数,weak_ptr不参与资源的管理,不支持operator
和operator->。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2094962.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

World of Warcraft [CLASSIC][80][Grandel] Call to Arms: Arathi Basin

PVP战场阿拉希盆地15人 /i 开局队伍分配&#xff1a;圣骑士飙车光环 /i [铁匠铺]坦克、治疗3个、输出6个&#xff08;10人组&#xff09; /i [伐木场]坦克、治疗、输出2&#xff08;4个人组&#xff09; /i [农场]留一个守&#xff08;1个人组&#xff09; /i 不要恋战&#x…

如何准确物理定位EMC Unity存储的磁盘位置

上周收到一个客户的咨询&#xff0c;问题是想主动更换一个Unity存储的磁盘&#xff0c;但不知道这个盘具体在存储的什么位置&#xff0c;有没有命令或者方法准确找到这个磁盘的物理位置&#xff1f; 以前也碰到过过类似的问题&#xff0c;但大部分是来自VNX存储。在现场让客户…

ChatGPT-4o:多领域创新应用的智能助手

ChatGPT-4o&#xff1a;多领域创新应用的智能助手 前言1. 数学建模&#xff1a;ChatGPT-4o的精确计算1.1 专业术语简介1.2 代码示例&#xff1a;线性规划问题问题描述代码实现运行结果 2. AI绘画&#xff1a;ChatGPT-4o的视觉创造力2.1 角色设计示例&#xff1a;火焰魔法师角色…

Leangoo领歌敏捷管理:助力敏捷高效协作,轻松实现Scrum敏捷转型

在当今快速变化的商业环境中&#xff0c;企业面临着前所未有的挑战。如何在激烈的竞争中保持领先&#xff1f;如何快速响应市场需求&#xff1f;答案就在于敏捷转型。而在这一过程中&#xff0c;有一个高效的敏捷工具至关重要——Leangoo领歌&#xff08;Leangoo领歌 - 免费一站…

盛京银行营收、利润双降下的负重难行,症结在哪儿?

撰稿|芋圆 来源|贝多财经 盛京银行自2020开年始&#xff0c;经营业绩除了在2022年稍有回暖外&#xff0c;均处于营收、利润双降的局面。 2024年半年报显示&#xff0c;盛京银行的资产总额为10683亿元&#xff0c;规模较2023年末收缩1.1%&#xff1b;营业收入46亿元&#xff0…

【前缀和算法】--- 进阶题目赏析

Welcome to 9ilks Code World (๑•́ ₃ •̀๑) 个人主页: 9ilk (๑•́ ₃ •̀๑) 文章专栏&#xff1a; 算法Journey 本篇我们来赏析前缀和算法的进阶题目。 &#x1f3e0; 和可被K整除的子数组 &#x1f4cc; 题目解析 和可被k整除的子数组 &#x1f4cc; …

软件单元测试工程模版化

一、简介 在汽车领域混了这么多年也做了不少项目&#xff0c;发现很多公司对软件单元测试和代码覆盖率测试根本不重视&#xff0c;或者开发流程就没有单元测试这个流程。但是有的客户需要评审单元测试这个流程&#xff0c;需要有相关的单元测试报告和代码覆盖率统计的报告。如…

百度 AI Studio 脚本任务篇,它不同于notebook任务是支持免费的, 脚本任务是需要算力卡的,更好的算力 支持四张显卡,

aistudio 脚本任务是需要算力卡的&#xff0c;是收费的一个项目&#xff0c;估计是运行效率更高&#xff0c;支持4张显卡&#xff0c;同时计算。 # -*- coding: utf-8 -*- """ 空白模板 """ ###### 欢迎使用脚本任务&#xff0c;首先让我们熟悉…

计算机毕设选题推荐-基于python的豆瓣电子图书数据可视化分析

&#x1f496;&#x1f525;作者主页&#xff1a;毕设木哥 精彩专栏推荐订阅&#xff1a;在 下方专栏&#x1f447;&#x1f3fb;&#x1f447;&#x1f3fb;&#x1f447;&#x1f3fb;&#x1f447;&#x1f3fb; 实战项目 文章目录 实战项目 一、基于python的豆瓣电子图书数…

插入排序代码实现(java)

简介&#xff1a; 也是一种简单的排序方法&#xff0c;其基本操作是将一条记录插入到已排好的有序表中&#xff0c;从而得到一个新的、记录数量增的有序表 说明&#xff1a; 拿一维数组来说&#xff0c;可以把第一个元素看成一个有序表&#xff0c;后面的元素看成无序表&am…

《中文Python穿云箭量化平台二次开发技术08》获取大盘涨跌家数、平均股价数据等来判断市场涨跌趋势,并在策略中自动控制多空交易

《中文Python穿云箭量化平台》是纯Python开发的量化平台&#xff0c;因此其中很多Python模块&#xff0c;我们可以自己设计新的量化工具&#xff0c;例如自己新的行情软件、新的量化平台、以及各种量化研究工具。 穿云箭自带指标公式源码运行模块&#xff0c;可以为其他量化平台…

莫比乌斯反演总结

目录 前置知识1.1 线性筛 (欧拉筛)1.2 整除分块 (数论分块)引理 1引理 2引理 3实现例 1例 2例 3例 4 1.3 数学知识积性函数莫比乌斯函数狄利克雷(Dirichlet)卷积 莫比乌斯反演2.1 公式2.2 常用~(唯一)~结论2.3 例题例 1例 2例 3例 4例 5练习 1练习 2练习 3练习 4 懵逼乌斯反演总…

配置nginx安全连接ssl(购买域名、获取ssl证书)

以前了解过ssl配置比较麻烦&#xff0c;需要弄挺多东西。 1、购买域名、获取ssl证书 2、安装nginx的ssl模块 3、配置config 1、购买域名、获取ssl证书 可以在腾讯云、阿里云购买域名&#xff0c;然后申请免费的ssl证书&#xff0c;因为免费的证书需要域名才能申请&#xff0…

docker安装配置、docker命令

一、CentOS7安装docker 1、安装 Docker CE 支持 64 位版本 CentOS 7&#xff0c;并且要求内核版本不低于 3.10&#xff0c; CentOS 7 满足最低内核的要求&#xff0c;所以我们在CentOS 7安装Docker。 卸载旧docker 如果之前安装过旧版本的Docker&#xff0c;可以使用下面命令…

Codeforces Round 964 (Div. 4) A-E Java题解

比赛地址 Dashboard - Codeforces Round 964 (Div. 4) - Codeforces A题 签到题 给一个两位数 求各位上的数字和 直接对10取余加上本来的数除以10 // 注意类名必须为 Main, 不要有任何 package xxx 信息 // package Dduo; import java.io.*; import java.math.*; import j…

22:差分线规则

1.那些线是差分对&#xff1a; ①有些特定模块就是差分线&#xff1a;USB&#xff0c;HDMI, 以太网口&#xff0c;LEDS等 设置差分对 Panel打开PCB 输入&#xfe62;和- 点击执行 对90欧姆差分对和100Ω差分对进行分类 设置差分对线宽 ①90ohm 由excel可知&a…

孩子自闭症的主要表现:探寻理解之门

自闭症&#xff0c;也称为孤独症&#xff0c;是一种复杂的神经发展障碍&#xff0c;它影响着孩子的社交互动、沟通能力以及行为模式。当家长注意到孩子出现自闭症倾向时&#xff0c;及时识别并寻求专业帮助至关重要。以下是孩子自闭症的一些主要表现&#xff0c;希望能为家长提…

西安电子科技大学研究生新生大数据

西安电子科技大学研究生新生大数据&#xff0c;来自卓越工程学院—杭州研究院 杭研不少来自双非院校&#xff0c;西电也不怎么歧视双非的

游戏开发设计模式之模板方法模式

目录 模板方法模式在游戏开发中的具体应用案例是什么&#xff1f; 如何在不同类型的游戏&#xff08;如角色扮演游戏、策略游戏等&#xff09;中实现模板方法模式&#xff1f; 模板方法模式与其他设计模式&#xff08;如观察者模式、状态模式等&#xff09;相比&#xff0c;…

实战项目:俄罗斯方块(二)

文章目录 &#x1f34a;自我介绍&#x1f34a;俄罗斯方块数据存储三维数组的简单介绍俄罗斯方块数组的设计类型的设计初始值的方块库的设计输出指定位置的图形输出每种图形及其转换形式代码 你的点赞评论就是对博主最大的鼓励 当然喜欢的小伙伴可以&#xff1a;点赞关注评论收藏…