C++--智能指针

news2024/11/15 21:28:20

在这里插入图片描述

普通指针创建动态内存的问题:

1.new和new[]的内存需要使用delete和delete []释放。

2.有时忘记释放内存。

3.不知该在何时释放内存。

智能指针的优点:

在不需要对象时自动释放对象,从而避免内存泄漏和其他与内存管理相关的问题。

智能指针有:unique_ptr,shared_ptr和weak_ptr

C++中几种常见的智能指针

std::unique_ptr

独占所有权:unique_ptr 拥有其所指向对象的独占所有权。同一时间内只能有一个 unique_ptr 指向某个对象。

移动语义:当 unique_ptr 被赋值或移动到另一个 unique_ptr 时,原 unique_ptr 会失去对对象的所有权,而新 unique_ptr 会获得所有权。

自定义删除器:可以为 unique_ptr 指定一个自定义的删除器,以在删除对象时执行特定的操作。

不支持复制操作:由于 unique_ptr 拥有独占所有权,因此它不支持复制构造函数和复制赋值操作符。


std::shared_ptr

共享所有权:多个 shared_ptr 可以指向同一个对象,并共享其所有权。当最后一个拥有该对象的 shared_ptr 被销毁或重置时,对象才会被删除。

引用计数:shared_ptr 使用引用计数来跟踪指向某个对象的 shared_ptr 的数量。当引用计数变为0时,对象会被自动删除。

循环引用问题:由于 shared_ptr 的共享所有权特性,如果不当使用可能会导致循环引用问题,从而阻止对象被正确删除。为了解决这个问题,可以使用 std::weak_ptr。


std::weak_ptr

弱引用:weak_ptr 是对 shared_ptr 所管理对象的弱引用,它不增加对象的引用计数。因此,它不会阻止对象被 shared_ptr 删除。

解决循环引用:weak_ptr 通常用于解决由 shared_ptr 引起的循环引用问题。通过使用 weak_ptr 代替其中一个 shared_ptr,可以确保在循环引用中至少有一个指针不会增加引用计数。

访问原始指针:虽然 weak_ptr 本身不拥有对象,但它可以安全地访问对象的原始指针(通过调用 lock() 方法)。如果对象仍然存在,lock() 将返回一个指向该对象的 shared_ptr;否则,将返回一个空的 shared_ptr。
以下是一些可能导致内存泄漏的情况:

循环引用:在使用std::shared_ptr时,如果两个或更多的智能指针相互持有对方的引用(形成循环),则它们将永远不会被释放,从而导致内存泄漏。为了避免这种情况,可以使用std::weak_ptr来打破循环。

自定义删除器:如果你为智能指针提供了一个自定义删除器,并且这个删除器存在缺陷,那么可能会导致内存泄漏。例如,删除器可能只是简单地调用delete,但如果你的对象需要特殊的清理逻辑(如关闭文件句柄或释放其他资源),那么这些资源可能不会被正确释放。


unique_ptr

unique_ptr 独占智能指针。
它独占指向的对象,也就是说,某个时刻只有一个 unique_ptr 指向一个给定对象,当unique_ptr 被销毁时,它所指向的对象也被销毁。
需要包含头文件: #include


class Student
{
public:
    string m_name;//学生姓名
    Student(const string& name = "") :m_name(name)//构造函数
    {
        cout << "构造函数:" << m_name << ",被创建" << endl;
    }
    ~Student()
    {
        cout << "析构函数:" << m_name << ",被释放了" << endl;
    }
};

int main()
{
    auto p1 = new Student("刘备");//利用new创建一个新对象

    //delete p1; //忘记释放对象,出现内存泄漏

    return 0;
}

在这里插入图片描述
类定义
下面是unique_ptr的部分源码.

template< class T, class Del = default_delete<T> >
class unique_ptr {
public:
    unique_ptr( );               //默认构造函数
    explicit unique_ptr( pointer Ptr ); //构造函数,不允许隐式转换
    unique_ptr (pointer Ptr, typename remove_reference<Del>::type&& Deleter);//提供delete 方法
    unique_ptr( const unique_ptr& Right) = delete; //不允许拷贝构造
    unique_ptr& operator=(const unique_ptr& Right ) = delete;//不允许赋值 =
};

说明:unique_ptr是"唯一"类型的智能指针,不允许多个智能指针指向同一个对象。因此不支持 = 和 拷贝构造。

初始化

//方法一:
unique_ptr<Student> p1(new Student("刘备"));// 分配内存并初始化。
//方法二:
unique_ptr<Student> p2 = make_unique<Student>("曹操"); // C++14标准提供,优先使用
//方法三:
Student* ps = new Student("孙权");
unique_ptr<Student> p3(ps);// 用已存在的地址初始化。

使用智能指针
智能指针和普通指针访问对象成员的方式是一样的,通过->访问成员。例如


class Student
{
public:
    Student(const string& name = ""):m_name(name)//构造函数
    {
        cout << "构造函数:" << m_name << ",被创建" << endl;
    }
    ~Student()
    {
        cout << "析构函数:" << m_name << ",被释放了" << endl;
    }
    void show()const
    {
        cout << "学生名字:" << m_name << endl;
    }
private:
    string m_name;//学生姓名
};

int main()
{
    Student* p1 = new Student("刘备");
    unique_ptr<Student> p2(new Student("曹操"));
    p1->show(); //访问p1的成员函数
    p2->show();//访问p2的成员函数

    return 0;
}

在这里插入图片描述

作为函数参数(经常使用)
传引用。不能传值(因为unique_ptr没有拷贝构造函数)

void test(const unique_ptr<Student>& p)
{
    cout << "test函数" << endl;
    p->show();
}

int main()
{
    unique_ptr<Student> p1(new Student("曹操"));
    test(p1);

    return 0;
}

添加删除器(面试可能问)
可以通过函数,仿函数,lambda表达式,自定义删除器。

//自定义删除器,普通函数
void DeleteFunc(Student* ps)
{
    cout << "自定义删除器(普通函数)" << endl;
    delete ps;//如果漏写这句就可能出现内存泄漏,但这是你的代码问题,不是智能指针问题
}

//自定义删除器,仿函数
class DeleteClass
{
public:
    void operator()(Student* ps) //重载()
    {
        cout << "自定义删除器(仿函数)" << endl;
        delete ps;
    }
};

//自定义删除器,lambda
auto DeleteLambda = [](Student* ps)
{
    cout << "自定义删除器(lambda)" << endl;
    delete ps;
};

int main()
{
    //自定义删除器,普通函数  
    unique_ptr<Student,decltype(DeleteFunc)*> p1(new Student("刘备"), DeleteFunc);

    //自定义删除器,仿函数
    unique_ptr<Student, DeleteClass> p2(new Student("曹操"), DeleteClass());

    //自定义删除器,lambda
    unique_ptr<Student, decltype(DeleteLambda)> p3(new Student("孙权"), DeleteLambda);

    return 0;
}

shared_ptr

shared_ptr共享智能指针,多个shared_ptr可以指向相同的对象,在内部有一个关联的计数器。通常称其为引用计数。
**引用计数是一种内存管理技术,用于跟踪共享资源的引用情况,并在引用为0时进行释放。引用计数使用一个计数器来统计当前指针指向的对象被多少类的对象所使用,即记录指针指向对象被引用的次数。**当新的shared_ptr与对象关联时,引用计数加1。当shared_ptr结束时,引用计数减1。当引用计数变为0时,表示没有任何shared_ptr与对象关联,则释放该对象。
2.1 类定义
初始化

//方法一:
shared_ptr<Student> p1(new Student("孙悟空"));// 分配内存并初始化。
//方法二:
shared_ptr<Student> p2 = make_unique<Student>("猪八戒"); // C++14标准,推荐使用
//方法三:
Student* ps = new Student("沙僧");
shared_ptr<Student> p3(ps);// 用已存在的地址初始化。
//方法四:
shared_ptr<Student> p4(p3);  //利用p3初始化p4
//方法五:
shared_ptr<Student> p5 = p3;  //利用p3初始化p5
详细代码如下:

详细代码如下

#include <iostream>
using namespace std;

class Student
{
public:
    Student(const string& name = ""):m_name(name)//构造函数
    {
        cout << "构造函数:" << m_name << ",被创建" << endl;
    }
    ~Student()
    {
        cout << "析构函数:" << m_name << ",被释放了" << endl;
    }
    void show()const
    {
        cout << "学生名字:" << m_name << endl;
    }
private:
    string m_name;//学生姓名
};

int main()
{
    //方法一:
    shared_ptr<Student> p1(new Student("孙悟空"));// 分配内存并初始化。
    //方法二:
    shared_ptr<Student> p2 = make_unique<Student>("猪八戒"); // C++14标准,推荐使用
    //方法三:
    Student* ps = new Student("沙僧");
    shared_ptr<Student> p3(ps);// 用已存在的地址初始化。
    //方法四:
    shared_ptr<Student> p4(p3);  //利用p3初始化p4
    //方法五:
    shared_ptr<Student> p5 = p3;  //利用p3初始化p5
    cout << "孙悟空的引用计数:"<<  p1.use_count() << endl;
    cout << "猪八戒的引用计数:" << p2.use_count() << endl;
    cout << "沙僧的引用计数:" << p3.use_count() << endl;

    return 0;
}

作为函数参数
传值和传引用都可以,建议传引用。

void test01(shared_ptr<Student> p)//传值
{
    p->show();
}

void test02(shared_ptr<Student>& p) //传引用
{
    p->show();
}

int main()
{
    shared_ptr<Student>p = make_shared<Student>("菩提老祖");
    test01(p);
    test02(p);

    return 0;
}

添加删除器

//自定义删除器,普通函数
void DeleteFunc(Student* ps)
{
    cout << "自定义删除器(普通函数)" << endl;
    delete ps;
}

//自定义删除器,仿函数
class DeleteClass
{
public:
    void operator()(Student* ps) //重载()
    {
        cout << "自定义删除器(仿函数)" << endl;
        delete ps;
    }
};

auto DeleteLambda = [](Student* ps)
{
    cout << "自定义删除器(lambda)" << endl;
    delete ps;
};

int main()
{
    shared_ptr<Student> p1(new Student("孙悟空"), DeleteFunc);
    shared_ptr<Student> p2(new Student("猪八戒"), DeleteClass());
    shared_ptr<Student> p3(new Student("沙僧"), DeleteLambda);

    return 0;
}

shared_ptr,weak_ptr

shared_ptr之间的循环引用问题
在C++中,shared_ptr是用来管理动态分配对象的生命周期的智能指针,它使用引用计数来确保当最后一个指向对象的shared_ptr被销毁时,对象本身也会被销毁。然而,当shared_ptr之间形成循环引用时,这个问题就变得复杂了。
循环引用发生在两个或多个shared_ptr相互引用对方所管理的对象时。由于每个shared_ptr都持有一个引用计数,只要引用计数不为零,它所指向的对象就不会被销毁。因此,即使程序的其他部分不再需要这些对象,它们也不会被释放,从而导致内存泄漏。

例如:

class A;
class B;

class A {
public:
    A(){
        m_a = new int[10];//动态创建10个元素
        cout << "A()" << endl;
    }
    ~A() {
        delete[]m_a;//释放内存
        cout << "~A()" << endl;
    }
    shared_ptr<B> b_ptr;
   
private:
    int* m_a;//模拟动态创建的内存
};

class B {
public:
    shared_ptr<A> a_ptr;
    B() {
        m_b = new int[10];//动态创建10个元素
        cout << "B()" << endl;
    }
    ~B() {
        delete[]m_b;//释放内存
        cout << "~B()" << endl;
    }
private:
    int* m_b;
};

int main() {
    auto a = std::make_shared<A>();
    auto b = std::make_shared<B>();

    a->b_ptr = b;
    b->a_ptr = a;

    // a 和 b 的引用计数都是 2,因为它们相互引用  
    // 当 a 和 b 离开作用域时,它们的引用计数不会降到 0  
    // 因此,A 和 B 的实例不会被销毁,导致内存泄漏  
    return 0;
}

weak_ptr的作用
为了解决shared_ptr之间的循环引用问题,C++标准库提供了weak_ptr。weak_ptr是对shared_ptr所管理对象的一种弱引用,它不会增加对象的引用计数。因此,当最后一个shared_ptr被销毁时,即使还有weak_ptr指向该对象,对象也会被销毁。
修改上面的例子,使用weak_ptr来打破循环引用

class A;
class B;

class A {
public:
    A(){
        m_a = new int[10];//动态创建10个元素
        cout << "A()" << endl;
    }
    ~A() {
        delete[]m_a;//释放内存
        cout << "~A()" << endl;
    }
    weak_ptr<B> b_ptr;
   
private:
    int* m_a;//模拟动态创建的内存
};

class B {
public:
    shared_ptr<A> a_ptr;
    B() {
        m_b = new int[10];//动态创建10个元素
        cout << "B()" << endl;
    }
    ~B() {
        delete[]m_b;//释放内存
        cout << "~B()" << endl;
    }
private:
    int* m_b;
};

int main() {
    auto a = std::make_shared<A>();
    auto b = std::make_shared<B>();

    a->b_ptr = b;
    b->a_ptr = a;

    // a 和 b 的引用计数都是 2,因为它们相互引用  
    // 当 a 和 b 离开作用域时,它们的引用计数不会降到 0  
    // 因此,A 和 B 的实例不会被销毁,导致内存泄漏  
    return 0;
}

使用weak_ptr时需要小心,因为它不保证所指向的对象仍然存在。在访问weak_ptr所指向的对象之前,通常通过调用lock函数测试其有效性,并将其升级为shared_ptr。如果lock()返回空指针,则意味着原始对象已经被销毁。


本篇完!

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

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

相关文章

可转债之强赎条款

摘要&#xff1a;每天学习一点金融小知识 做可转债投资&#xff0c;强赎风险是特别需要注意的&#xff0c;若投资者没有及时采取措施&#xff0c;就有可能造成很大的损失。本文从可转债的定义、强赎条款的原因及强赎的情况几个方面来介绍下可转债的强赎条款。 什么是可转换债券…

算法——同步算法

在力扣有这样一道题求交集&#xff0c;与此类似的还有求差集&#xff0c;相关的解法有很多。我这里提供一种思路&#xff1a;利用C的容器set对这两个数组去重&#xff0c;遍历数组插入set即可去重。再同时遍历比较set的每个元素。 代码实现很简单&#xff0c;如下所示&#xff…

【第四届会后4个月检索】第五届计算机网络安全与软件工程国际学术会议(CNSSE 2025)

第五届计算机网络安全与软件工程国际学术会议&#xff08;CNSSE 2025&#xff09; 2025 5th International Conference on Computer Network Security and Software Engineering 重要信息 大会官网&#xff1a;www.cnsse.org 大会时间&#xff1a;2025年2月21-23日 会议地点&…

CTF-PWN-kernel-栈溢出(retuser rop pt_regs ret2dir)

文章目录 参考qwb2018 core检查逆向调试打包上传测试脚本retuserkernel ropinit_credcommit_creds( prepare_kernel_cred(0) )开启KPTI利用swapgs_restore_regs_and_return_to_usermode开启KPTI利用SIGSEGVrop设置CR3寄存器再按照没有KPTI返回 kernel rop ret2userpt_regs 构造…

使用命令行修改Ubuntu 24.04的网络设置

Ubuntu里&#xff0c;使用命令行下修改IP地址&#xff0c;网上有很多方案&#xff0c;我最终觉得这个方案&#xff08;使用Netplan&#xff09;最好&#xff0c;最根本&#xff0c;记录下来备查 1.使用命令ip link show 查看Ubuntu上可以使用的网络接口名称 2.查找Netplan的配…

全志T527 适配YT8531 双以太网

一、确认硬件接口 phy1&#xff1a; phy2&#xff1a; PHY 地址设置&#xff1a; YT8531 的地址由上图所示的三个管脚外接 ( 或内部默认 ) 电阻来配置。外部不接上下拉电阻时&#xff0c;内部默认 phy 地址为 000( 十进制 0) &#xff1b;若外接电阻&#xff0c;例如上图所接…

前端面试题33(实时消息传输)

前端实时传输协议主要用于实现实时数据交换&#xff0c;特别是在Web应用中&#xff0c;它们让开发者能够构建具有实时功能的应用&#xff0c;如聊天、在线协作、游戏等。以下是几种常见的前端实时传输协议的讲解&#xff1a; 1. Short Polling (短轮询) 原理&#xff1a;客户…

二分查找3

1. 有序数组中的单一元素&#xff08;540&#xff09; 题目描述&#xff1a; 算法原理&#xff1a; 二分查找解题关键就在于去找到数组的二段性&#xff0c;这里数组的二段性是从单个数字a开始出现然后分隔出来的&#xff0c;如果mid落入左半部分那么当mid为偶数时nums[mid1]…

搞清楚[继承],易如反掌

穷不失义&#xff0c;达不离道。——孔丘《论语》 继承 1、简单理解2、继承2、1、继承的概念2、2、继承定义2、3、基类和派生类对象赋值转换2、4、继承中的作用域2、5、派生类默认成员函数2、6、继承中的特点2、6、1、友元2、6、2、静态成员2、6、3、菱形继承及菱形虚拟继承 3、…

【开源】基于RMBG的一键抠图与证件照制作系统【含一键启动包】

《博主简介》 小伙伴们好&#xff0c;我是阿旭。专注于人工智能、AIGC、python、计算机视觉相关分享研究。 ✌更多学习资源&#xff0c;可关注公-仲-hao:【阿旭算法与机器学习】&#xff0c;共同学习交流~ &#x1f44d;感谢小伙伴们点赞、关注&#xff01; 《------往期经典推…

2024浙江外国语学院汉语桥线上项目 “在杭州,看见更好的中国”开班

7月9日上午&#xff0c;由教育部中外语言交流合作中心主办、浙江外国语学院国际商学院承办的2024汉语桥“在杭州&#xff0c;看见更好的中国”线上项目正式启动。项目负责人何骅老师及汉语桥教师团队&#xff0c;与来自越南、缅甸、日本、俄罗斯的100名学员相聚云端&#xff0c…

JavaSE学习笔记第二弹——对象和多态(上)

目录 面向对象基础 面向对象程序设计的定义 类的基本结构 成员变量 成员方法 方法定义与使用 设计练习 方法重载 构造方法 静态变量和静态方法 String和StringBuilder 基本含义 区别 总结 今天我们继续来学习JavaSE&#xff0c;扩展和Java相关的知识&#xff0c;…

当CNN遇上Mamba,高性能与高效率通通拿下!

传统视觉模型在处理大规模或高分辨率图像时存在一定限制&#xff0c;为解决这个问题&#xff0c;研究者们就最近依旧火热的Mamba&#xff0c;提出了Mamba结合CNN的策略。 这种结合可以让Mamba在处理长序列数据时既能够捕捉到序列中的时间依赖关系&#xff0c;又能够利用CNN的局…

android自定义键盘弹窗

样式布局 要在Android中自定义键盘弹窗&#xff0c;先要创建一个新的XML布局文件&#xff0c;用于定义键盘弹窗的外观和布局。例如&#xff0c;创建一个名为key_alert_dialog.xml的文件&#xff0c;并在其中添加所需的按钮和其他UI元素。 <?xml version"1.0" e…

7月9日学习打卡-回文链表,交叉链表

大家好呀&#xff0c;本博客目的在于记录暑假学习打卡&#xff0c;后续会整理成一个专栏&#xff0c;主要打算在暑假学习完数据结构&#xff0c;因此会发一些相关的数据结构实现的博客和一些刷的题&#xff0c;个人学习使用&#xff0c;也希望大家多多支持&#xff0c;有不足之…

海外多语言盲盒APP系统开发

随着盲盒的全球化发展&#xff0c;盲盒已经成为了一个热门行业&#xff0c;不仅深受我国消费者的青睐&#xff0c;更是深受海外消费者的喜爱。目前&#xff0c;盲盒出海已经成为了企业拓展市场的新机会。 在数字化时代&#xff0c;海外盲盒APP为企业提供了一个快速打开海外盲盒…

57、基于概率神经网络(PNN)的分类(matlab)

1、基于概率神经网络(PNN)的分类简介 PNN&#xff08;Probabilistic Neural Network&#xff0c;概率神经网络&#xff09;是一种基于概率论的神经网络模型&#xff0c;主要用于解决分类问题。PNN最早由马科夫斯基和马西金在1993年提出&#xff0c;是一种非常有效的分类算法。…

MyBatis框架学习笔记(一):MyBatis入门

1 MyBatis 介绍 1.1 官方文档 MyBatis 中文手册&#xff1a; &#xff08;1&#xff09;https://mybatis.org/mybatis-3/zh/index.html &#xff08;2&#xff09;https://mybatis.net.cn/ Maven 仓库&#xff1a; https://mvnrepository.com/ 仓库作用&#xff1a;需要…

android Dialog全屏沉浸式状态栏实现

在Android中&#xff0c;创建沉浸式状态栏通常意味着让状态栏背景与应用的主题颜色一致&#xff0c;并且让对话框在状态栏下面显示&#xff0c;而不是浮动。为了实现这一点&#xff0c;你可以使用以下代码片段&#xff1a; 1、实际效果图&#xff1a; 2、代码实现&#xff1a;…