C++ 4种智能指针的定义与使用——学习记录008

2023-09-14 19:40:38

1.智能指针

1.1作用

智能管理动态分配的内存,自动释放程序员new出来的内存,从而避免内存泄漏。

1.2原理

动态分配的内存交给有生命周期的对象处理,在对象过期时,内存的释放交给对象来处理。

1.3使用方法

#include<memory>
auto_ptr<类型> 变量名称(new 类型);

auto_ptr<string> str(new string("string型智能指针测试"));
auto_ptr<vector<int>> v(new vecotr<int>);
auto_ptr<int> array(new int[10]);

智能指针内部实现了*,->运算符重载,可以像普通指针一样使用。

1.4常用函数

1) get()函数:获取指针返回

auto_ptr<Test> test(new Test);

Test *tmp = test.get();        // 获取指针返回

2) release()函数:取消智能指针对动态内存的托管,会将内存的地址返回

auto_ptr<Test> test(new Test);
Test *tmp = test.release();        // 取消智能指针对动态内存的托管
delete tmp;            // 手动释放

3) reset()函数:释放智能指针托管的指针内存

autp_ptr<Test> test(new Test);
test.reset();                 // 释放智能指针托管的指针内存,将其置为NULL
test.reset(new Test());       // 释放智能指针托管的指针内存,并指向形式参数中的内存地址

存在的问题:

1.复制或者赋值都会改变资源的所有权

2.在STL容器中使用auto_ptr存在风险,因为容器中元素必须可复制和可赋值

3.不支持对象数组管理

1)复制和赋值的资源所有权问题

// auto_ptr 被C++11抛弃的主要原因
auto_ptr<string> p1(new string("I'm Li Ming!"));
auto_ptr<string> p2(new string("I'm age 22."));

cout << "p1:" << p1.get() << endl;
cout << "p2:" << p2.get() << endl;

/*
 TODO p2赋值给p1后,首先p1会先将自己原先托管的内存释放掉,然后接收托管p2所托管的内存,
 然后p2所托管的指针为NULL,也就是p1托管了p2托管的内存,而p2放弃了托管。
 */ 
p1 = p2;    
cout << "p1 = p2 赋值后:" << endl;
        // 结构托管
cout << "p1:" << p1.get() << endl;        // 输出p1托管的地址
cout << "p2:" << p2.get() << endl;
        // 输出为null空地址
cout << *p2.get() << endl;                 // 出错——空地址取值运算

2)STL容器中使用auto_ptr的风险,首先需要对auto_ptr进行move修饰

vector<auto_ptr<string>> vec;
auto_ptr<string> p3(new string("I'm P3"));
auto_ptr<string> p4(new string("I'm P4"));

// 必须使用std::move修饰成右值,才可以进行插入到容器中
vec.push_back(std::move(p3));
vec.push_back(std::move(p4));

cout << "vec.at(0):" <<  *vec.at(0) << endl;
cout << "vec[1]:" <<  *vec[1] << endl;


// 出错
// 在这里进行赋值,赋值成功后,vec[1]智能指针将被释放,输出将会出错
vec[0] = vec[1];    // 如果进行赋值,问题又回到了上面一个问题中。
cout << "vec.at(0):" << *vec.at(0) << endl;
cout << "vec[1]:" << *vec[1] << endl;

3)不支持对象数组管理

auto_ptr<int[]> array(new int[5]);    // 出错

2. C++11 智能指针新特性

1. unique_ptr独占对象所有权,没有使用引用计数,整体性能良好

2. shared_ptr共享对象的所有权,使用引用计数,性能较差

3. weak_ptr配合shared_ptr,解决循环引用问题

unique_ptr指针替换了auto_ptr智能指针,特性:

1.基于排他所有权模式,两个指针不能指向同一个资源。

2.无法进行左值unique_ptr赋值构造,也无法进行左值复制赋值操作,但允许临时右赋值和构造赋值。

3.在容器中保存指针是安全的

4.unique_ptr可以指向一个数组

5.unique_ptr需要指定删除器的类型

shared_ptr特性

使用引用计数,每一个shared_ptr的拷贝都指向相同的内存,在最后一个shared_ptr析构时,被托管的内存才会释放。

shared_ptr共享管理的对象,同一时刻可以有多个shared_ptr拥有对象的所有权。

shared_ptr 的小问题:当有两个对象中成员变量都使用了一个shared_ptr指针,并将shared_ptr指针都指向了对方,会造成循环引用,使引用计数失效,从而导致内存泄漏。

/*
    1. 不能使用原始指针初始化多个shared_ptr
    2. 函数实参中不要创建shared_ptr指针       // 因为函数参数的计算顺序不一定每次相同,先定义后使用
    3. 不要通过shared_from_this()返回this指针    // 可能导致重复析构
*/

       int *ptr = new int;
    shared_ptr<int> p1(ptr);
    shared_ptr<int> p2(ptr);    // 出错

    /// 可能顺序:先new一个int,然后去调用g()函数,shared_ptr指针则不能实现对int的托管,造成了内存泄露
    func(shared_ptr<int>(new int), g());    // 可能会出问题

weak_ptr特性

1. waek_ptr 设计的目的是为了协助shared_ptr工作

2. weak_ptr 可以从一个shared_ptr或weak_ptr对象构造

3. weak_ptr 构造和析构不会引起计数的增加或减少

 // use_count()函数引用计数问题
    shared_ptr<int> sp(new int(10));
    weak_ptr<int> wp(sp);        // 定义weak_ptr,并用sp进行初始化
    cout << wp.use_count() << endl;    // 输出结果为 1
    
    // expired()指针有效性问题
     shared_ptr<int> sp(new int(10));
    weak_ptr<int> wp(sp);
     if (wp.expired()) {
        cout << "weak_ptr无效,内存已经释放" << endl;
        }
     else {
        cout << "weak_ptr有效,内存未释放" << endl;
        }
     
     // lock()获取托管内存
    auto sp = make_shared<int>(42);
    weak_ptr<int> wp = sp;
     auto spt = wp.lock();    // 获取shared_ptr

     if (wp.expired()) {
        cout << "wp有效" << *spt << endl;    // 输出内容
     }

weak_ptr解决shared_ptr中的循环引用问题

shared_ptr在两个对象中的同一成员变量同时使用了一个shared_ptr,并将shared_ptr指向对方时,会造成循环引用,使引用计数失效,从而造成内存泄漏。

只需要将两个对象中的其中一个使用的shared_ptr换成weak_ptr就可解决问题

#include <iostream>
#include <memory>
using namespace std;
class A;
class B;

class A {
public:
    std::weak_ptr<B> bptr; // 修改为weak_ptr
    ~A() {
    cout << "A is deleted" << endl;
    }
};

class B {
public:
    std::shared_ptr<A> aptr;
    ~B() {
    cout << "B is deleted" << endl;
    }
};
int main()
{
    {
    std::shared_ptr<A> ap(new A);
    std::shared_ptr<B> bp(new B);
    ap->bptr = bp;
    bp->aptr = ap;
    }
    cout<< "main leave" << endl;
    return 0;
}

更多推荐

OPTEE Benchmark框架

安全之安全(security²)博客目录导读OPTEE调试技术汇总目录一、序言二、Benchmark框架三、Benchmark实现细节1、设计概况2、时间戳源3、调用时序图4、添加自定义时间戳5、构建并运行Benchmark6、限制和进一步措施一、序言本节中描述的特性依赖于上游不可用的Linuxkernelpatch,

conda手动下载虚拟环境中的包

一.下载导入tar安装包1.准备安装包安装包可从官网下载,或直接拷贝已有虚拟环境中的包【补充】关于虚拟环境中包的存储路径打开cmd,输入condaconfig--show查看pkgs_dirs属性的值C:\Users\XXX>condaconfig--showpkgs_dirs:-D:\program\anaconda

PostgreSQL 事务&并发&锁

文章目录PostgreSQL事务大家都知道的ACID事务的基本使用保存点PostgreSQL并发并发问题MVCCPostgreSQL锁机制表锁行锁总结PostgreSQL事务大家都知道的ACID在日常操作中,对于一组相关操作,通常要求要么都成功,要么都失败。在关系型数据库中,称这一组操作为事务。为了保证整体事务的安全性

软件设计模式系列之十二——外观模式

在软件设计中,经常会遇到需要与复杂子系统进行交互的情况。为了简化客户端与子系统之间的交互,提高系统的可维护性和可用性,外观模式应运而生。外观模式(FacadePattern)是一种结构型设计模式,它提供一个统一的界面,用于访问系统中的一组相关接口,从而隐藏了系统的复杂性。在本文中,我们将深入探讨外观模式,包括其定义、举

员工犯错,就应该受惩罚吗?

先说结论,惩罚肯定是不能有的。我们应该指导员工减少犯错的概率。在项目开发过程中,总会遇到项目成员犯错的场景。根据所犯错误是否具有共性特征,我把问题归为二类,不同类型的错误,采取不同的应对措施。第一类:有共性的问题。第二类:没有共性的问题。共性的定义:一个人多次或者多个人都犯过的错误。第一类:有共性的问题。对于有共性的问

vue3 - 使用reactive定义响应式数据进行赋值时,视图没有改变,值已经改变的解决方案

问题:在Vue3.0中我们使用reactive()定义的响应式数据的时候,当是一个数组或对象时,我们直接进行赋值,发现数据已经修改成功,但是页⾯并没有自动渲染成最新的数据;这是为什么呢?就如同官网所说的reactive存在一些局限性:(官方的描述)原因:原因就是reactive函数会返回一个Proxy包装的对象,所以当

网络安全深入学习第六课——热门框架漏洞(RCE— Weblogic反序列化漏洞)

文章目录一、Weblogic介绍二、Weblogic反序列化漏洞历史三、Weblogic框架特征1、404界面2、登录界面四、weblogic常用弱口令账号密码五、Weblogic漏洞介绍六、Weblogic漏洞手工复现1、获取账号密码,这是一个任意文件读取的漏洞1)读取SerializedSystemIni.dat文

面向对象的分析与设计(精品课程)第二章作业

面向对象的分析与设计(精品课程)第二章作业一.单选题(共4题,30.4分)二.多选题(共1题,7.6分)三.填空题(共5题,38分)四.简答题(共3题,24分)一.单选题(共4题,30.4分)(单选题)UML中的事物包括结构事物、分组事物、注释事物和()。A实体事物B边界事物C控制事物D动作事物(单选题)UML中的4种

Linux设备树OF操作函数

OF操作函数我们知道Linux内核使用设备树的形式去描述芯片硬件设备节点的各种属性,设备树的树形结构可以层次化的组织这些节点属性。设备树源码属于脚本格式的文件,Linux内核无法直接使用脚本格式,所以最终使用时需要将设备树源码编译为二进制的“.dtb”格式,最终Uboot将".dtb"格式设备树传递给Linux内核使用

pdf文件可以压缩大小吗?pdf压缩方法分享

在日常生活和工作中,我们经常需要处理大量的PDF文件。有时候,一个PDF文件的大小可能超过了几十MB,甚至无法通过电子邮件发送。那么,如何有效地压缩PDF文件大小呢?本文将为你介绍三个简单易行的方法,帮助你轻松搞定PDF文件压缩大小的问题。首先,我们需要了解PDF文件的相关概念,PDF是一种可移植文档格式,可以跨平台、

Anaconda安装和配置 ---- 详细到家

安装1.打开Anaconda官网,选择对应版本,下载到对应目录即可或者进入:Indexof/anaconda/archive/|清华大学开源软件镜像站|TsinghuaOpenSourceMirror2.双击打开.exe文件,然后点击next;3.点击agree4.点击justme,然后next;5.在ChooseIn

热文推荐