当前位置: 首页 > news >正文

C++--智能指针

在这里插入图片描述

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

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());//自定义删除器,lambdaunique_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初始化p5cout << "孙悟空的引用计数:"<<  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()返回空指针,则意味着原始对象已经被销毁。


本篇完!

相关文章:

  • 洛谷 数学进制 7.9
  • C++八股(五)之Linux常用命令
  • Linux内核 -- 内存管理之scatterlist结构使用
  • 实现了 ApplicationContextAware 接口的bean可以接收到 ApplicationContext 的引用
  • 面试经典 150 题
  • 深入理解 Qt 的 `moveToThread`:提升多线程应用性能的关键
  • MySQL GROUP_CONCAT 函数详解与实战应用
  • 基于Java技术的B/S模式书籍学习平台
  • Python中的格式化输出
  • AntDesign上传组件upload二次封装+全局上传hook使用
  • 美国大带宽服务器租用优势和注意事项
  • git配置ssh-keygen -t rsa -c“xxxx@xxxx.com.cn出现Too many arguments.解决办法
  • ChatGPT提问提示指南PDF下载经典分享推荐书籍
  • react-fiber
  • C#运算符重载
  • [译]CSS 居中(Center)方法大合集
  • axios 和 cookie 的那些事
  • CAP理论的例子讲解
  •  D - 粉碎叛乱F - 其他起义
  • Java 实战开发之spring、logback配置及chrome开发神器(六)
  • Java程序员幽默爆笑锦集
  • Mac转Windows的拯救指南
  • mockjs让前端开发独立于后端
  • Mysql优化
  • vue2.0项目引入element-ui
  • vue中实现单选
  • 简单基于spring的redis配置(单机和集群模式)
  • 如何设计一个微型分布式架构?
  • 用Visual Studio开发以太坊智能合约
  • 移动端高清、多屏适配方案
  • ​Java基础复习笔记 第16章:网络编程
  • ​软考-高级-系统架构设计师教程(清华第2版)【第1章-绪论-思维导图】​
  • #pragam once 和 #ifndef 预编译头
  • #免费 苹果M系芯片Macbook电脑MacOS使用Bash脚本写入(读写)NTFS硬盘教程
  • (1)(1.9) MSP (version 4.2)
  • (2.2w字)前端单元测试之Jest详解篇
  • (2020)Java后端开发----(面试题和笔试题)
  • (2021|NIPS,扩散,无条件分数估计,条件分数估计)无分类器引导扩散
  • (C#)获取字符编码的类
  • (C语言)逆序输出字符串
  • (ibm)Java 语言的 XPath API
  • (附源码)springboot助农电商系统 毕业设计 081919
  • (经验分享)作为一名普通本科计算机专业学生,我大学四年到底走了多少弯路
  • (论文阅读26/100)Weakly-supervised learning with convolutional neural networks
  • (亲测成功)在centos7.5上安装kvm,通过VNC远程连接并创建多台ubuntu虚拟机(ubuntu server版本)...
  • (一)ClickHouse 中的 `MaterializedMySQL` 数据库引擎的使用方法、设置、特性和限制。
  • (原創) 如何安裝Linux版本的Quartus II? (SOC) (Quartus II) (Linux) (RedHat) (VirtualBox)
  • (转)淘淘商城系列——使用Spring来管理Redis单机版和集群版
  • .ai域名是什么后缀?
  • .NET Core WebAPI中封装Swagger配置
  • .net websocket 获取http登录的用户_如何解密浏览器的登录密码?获取浏览器内用户信息?...
  • .netcore如何运行环境安装到Linux服务器
  • .NET开源的一个小而快并且功能强大的 Windows 动态桌面软件 - DreamScene2
  • .sys文件乱码_python vscode输出乱码
  • @angular/cli项目构建--http(2)