c++面试三 -- 智能指针--7000字

一、智能指针

        C++ 中的智能指针是一种用于管理动态分配的内存的对象,它们可以自动进行内存管理,避免内存泄漏和悬挂指针等问题。

1. 悬挂指针

        悬挂指针(dangling pointer)是指在程序中仍然存在但已经不再指向有效内存地址的指针。悬挂指针通常是由于以下情况引起的:

        1. 释放内存后未将指针置空:

        当使用 deletefree 等方法释放了指针指向的内存后,如果未将指针置空(即将指针设置为 nullptrNULL),则该指针仍然保留之前的内存地址,成为悬挂指针。

int* ptr = new int(42);
delete ptr;
// ptr现在成为悬挂指针,它指向的内存已经被释放
        2. 超出作用域的引用:

        当指针指向的对象超出了其作用域(例如指向了一个局部变量),并且该对象的内存被释放,那么指针就会成为悬挂指针。

int* danglingPtr;
{
    int value = 42;
    danglingPtr = &value;
} // value的作用域结束,danglingPtr成为悬挂指针

        3. 指向已经被销毁的对象:

        如果指针指向的对象在其生命周期内被销毁了,那么指针就成为悬挂指针。

int* danglingPtr;
{
    std::unique_ptr<int> ptr = std::make_unique<int>(42);
    danglingPtr = ptr.get();
} // ptr超出作用域,其指向的对象被销毁,danglingPtr成为悬挂指针

2.野指针

        野指针是指指针指向的内存地址是随机的、未初始化的,或者指向的内存区域未经过分配。野指针可能是在创建指针后没有给它赋值(即未初始化指针),或者是在释放内存后未将指针置空,但继续使用该指针。

        野指针指向的内存地址通常是随机的,因此访问野指针可能会导致程序崩溃、数据损坏或安全漏洞。

        悬挂指针是指针仍然保留着之前指向的地址,但该地址已经不再有效;而野指针是指针指向的地址是随机的或未经过初始化。在编程中,应该尽量避免出现悬挂指针和野指针。

3.智能指针

C++现代实用教程:智能指针_哔哩哔哩_bilibili

 

3.1 unique_ptr指针 
        3.1.1 特点:

 3.1.2 创建

栈上:出作用域之后会自动调用析构函数

//stack
Cat c1("OK");
c1.cat_info(); 
{
    Cat c1("OK");
    c1.cat_info();
}

堆上:需要手动delete释放(不安全)

Cat *c_p1 = new Cat("yy");
int *i_p1 = new int(100);
c_p1->cat_info();
{
     int *i_p1 =  new int(200); // 重新声明,但是是在局部作用域中,与外部不同,结果为100。需要delete两次
     Cat *c_p1 new Cat("yy_scope");
     c_p1->cat_info();
     delete c_p1; 
     delete i_pi;
}
 delete c_p1; 
 delete i_pi;
Cat *c_p1 = new Cat("yy");
int *i_p1 = new int(100);
c_p1->cat_info();
{
     i_p1 =  new int(200); // 不重新声明,只是修改了值,结果为200  ,只用delete一次
     Cat *c_p1 = new Cat("yy_scope");
     c_p1->cat_info();
     delete c_p1; 
     delete i_pi;
}
 delete c_p1; 

unique_ptr创建的三种方式:

1. 通过原始指针创建

Cat *c_p2 =new Cat("yz");
std::unique_ptr<Cat> u_c_p2(c_p2};

// 此时原始指针还能用,需要进行销毁,否则不满足独占指针要求,否则如下
//c_p2->cat_info();
//u_c_p2->cat_info();
//c_p2->set_cat_name("ok");
//u_c_p2->cat_info();


// 销毁
c_p2 = nullptr;
c_p2 =nullptr;
u_c_p2->cat_info();

2. 使用new创建

std::unique_ptr<Cat> u_c_p3{new Cat("dd")};
u_c_p3->cat_info();
u_c_p3->set_cat_name("oo");
u_c_p3->cat_info();

 3.使用std::make_unique

std::unique_ptr<Cat> u_c_p4 =make_unique<Cat>();
u_c_p4->cat_info();
u_c_p4->set_cat_name("po");
u_c_p4->cat_info();

4. 移动语义创建

可以通过使用移动语义将一个已有的 std::unique_ptr 赋值给另一个 std::unique_ptr

#include <memory>

std::unique_ptr<int> u_c_p5 = std::move(u_c_p4 );
3.1.3  get()和常量类型
std::unique_ptr<int> u_i_p4 =make_unique<int>(200);
cout << "int address" << u_i_p4 .get() << endl; //get 获取原始指针或者地址
cout<< * u_i_p4<<endl;  // 打印值
3.2 unique_ptr和函数调用    (资源的所属权问题)

3.2.1 Passing by value  通过值传递
void do_with_cat_pass_value(std::unique_ptr<Cat> c){
    c->cat_info();
}

int main(){

    std::unique_ptr<Cat> c1 = make_unique<Cat>("ff");
    // 1.  使用 std::move 转移资源的所有权给函数,此时c1不再拥有资源的所有权了
    do_with_cat_pass_value(std::move(c1));

    // 2. 直接将参数传入make_unique ,将自动转换成move
    do_with_cat_pass_value(std::make_unique<Cat>());  // move

}
    
    
3.2.2 Passing by reference  通过引用传递,可以修改值
void do_with_cat_pass_ref(std::unique_ptr<Cat> &c){
    c->set_cat_name("oo");
    c->cat_info();
    c.reset();   // 释放先前所拥有的对象,不再指向任何对象
}

int main(){

    std::unique_ptr<Cat> c2 = make_unique<Cat>("ff");
    // 不用使用move,直接c2
    do_with_cat_pass_value(c2);
    
}
void do_with_cat_pass_ref(const std::unique_ptr<Cat> &c){
    c->set_cat_name("oo");
    c->cat_info();
   // c.reset();   // 释放先前所拥有的对象,不再指向任何对象   不能使用了
}

int main(){

    std::unique_ptr<Cat> c2 = make_unique<Cat>("ff");
    // 不用使用move,直接c2
    do_with_cat_pass_value(c2);
    c2 ->cat->info();
    
}

3.3.3  Return by Value
std::unique_ptr<Cat> get_unique_ptr(){
    std::unique_ptr<Cat> p_dog = std:: make_unique<Cat>("Local cat");
    cout <<p_dog.get()<<endl;
    cout <<&p_dog <<endl;
    return p_dog;
}
// 链式

get_unique_ptr->cat_info();
  1. p_dog.get(): 这个表达式返回指向 std::unique_ptr 管理的对象的原始指针。get() 函数是 std::unique_ptr 类的成员函数,它返回一个指向被管理对象的原始指针。使用 get() 可以获取 std::unique_ptr 所拥有的对象的原始指针,但是请注意,这个原始指针不包含所有权信息,因此需要谨慎使用,特别是不要手动释放内存。

  2. &p_dog: 这个表达式返回的是指向 std::unique_ptr 对象本身的指针,即指向 std::unique_ptr 对象的地址。& 是取地址运算符,它返回变量的地址。std::unique_ptr 是一个对象,因此 &p_dog 返回的是指向 std::unique_ptr 对象的指针

3.3 shared_ptr 计数指针/共享指针

        在实际的 C++ 开发中,我们经常会遇到诸如程序运行中突然崩溃、程序运行所用内存越来越多最终不得不重启等问题,这些问题往往都是内存资源管理不当造成的。比如:

        有些内存资源已经被释放,但指向它的指针并没有改变指向(成为了野指针),并且后续还在使用;
        有些内存资源已经被释放,后期又试图再释放一次(重复释放同一块内存会导致程序运行崩溃);
        没有及时释放不再使用的内存资源,造成内存泄漏,程序占用的内存资源越来越多。
        智能指针shared_ptr 是存储动态创建对象的指针,其主要功能是管理动态创建对象的销毁,从而帮助彻底消除内存泄漏和悬空指针的问题。

shared_ptr的原理和特点
        基本原理:就是记录对象被引用的次数,当引用次数为 0 的时候,也就是最后一个指向该对象的共享指针析构的时候,共享指针的析构函数就把指向的内存区域释放掉。

        特点:它所指向的资源具有共享性,即多个shared_ptr可以指向同一份资源,并在内部使用引用计数机制来实现这一点。

        共享指针内存:每个 shared_ptr 对象在内部指向两个内存位置:

        指向对象的指针;
用于控制引用计数数据的指针。
        1.当新的 shared_ptr 对象与指针关联时,则在其构造函数中,将与此指针关联的引用计数增加1。

        2.当任何 shared_ptr 对象超出作用域时,则在其析构函数中,它将关联指针的引用计数减1。如果引用计数变为0,则表示没有其他 shared_ptr 对象与此内存关联,在这种情况下,它使用delete函数删除该内存。

        shared_ptr像普通指针一样使用,可以将*和->与 shared_ptr 对象一起使用,也可以像其他 shared_ptr 对象一样进行比较;

3.3.1 常量类型
int main(){
    std::shared_ptr<int> i_p_1 =make_shared<int>(10);
    // std::shared_ptr<int> i_p_2 =make_shared<int>{new int(10)};


    // copy
    std::shared_ptr<int> i_p_2 =i_p_1;
    cout<< "use cout :" << *i_p_1.use_cout() <<endl;  // 1
    cout<< "use cout :" << *i_p_2.use_cout() <<endl;  // 1


    // change   两个指针指向同一个内存
    *i_p_2 =30;

    cout<< ":" << *i_p_1 <<endl;  // 30
    cout<< ":" << *i_p_2 <<endl;  // 30


    // 将i_p_2置为nullptr
    i_p_2=nullptr
    cout<< "use cout :" << *i_p_1.use_cout() <<endl;  // 1
    cout<< "use cout :" << *i_p_2.use_cout() <<endl;  // 0

     // 将i_p_1置为nullptr
    std::shared_ptr<int> i_p_3 =i_p_1;
    i_p_1=nullptr
    
    cout<< "use cout :" << *i_p_1.use_cout() <<endl;  // 0
    cout<< "use cout :" << *i_p_2.use_cout() <<endl;  // 2
     cout<< "use cout :" << *i_p_3.use_cout() <<endl;  // 2

    cout<< "value:" << *i_p_1 <<endl;
    cout<< "use cout :" << *i_p_1.use_cout() <<endl;
    return 0;
}
3.3.2 自定义类型
// 自定义类型
std::shared_ptr<Cat> c_p_1 =make_shared<Cat>();

cout<< "c_p_1 use cout :" << c_p_1.use_count() << endl;


std::shared_ptr<Cat> c_p_2 =c_p_1;
std::shared_ptr<Cat> c_p_3 =c_p_1;

cout<< "c_p_1 use cout :" << c_p_1.use_count() << endl;
cout<< "c_p_2 use cout :" << c_p_2 .use_count() << endl;
cout<< "c_p_3 use cout :" << c_p_3 .use_count() << endl;
3.3.3 make_shared的构建方法

      (1).构造函数创建

1.shared_ptr<T> ptr;//ptr 的意义就相当于一个 NULL 指针
2.shared_ptr<T> ptr(new T());//从new操作符的返回值构造
3.shared_ptr<T> ptr2(ptr1);    // 使用拷贝构造函数的方法,会让引用计数加 1
                               //shared_ptr 可以当作函数的参数传递,或者当作函数的返回值返回,这个时候其实也相当于使用拷贝构造函数。
4./*假设B是A的子类*/
shared_ptr<B> ptrb(new B());
shared_ptr<A> ptra( dynamic_pointer_cast<A>(ptrb) );//从 shared_ptr 提供的类型转换 (cast) 函数的返回值构造
5./* shared_ptr 的“赋值”*/
shared_ptr<T> a(new T());
shared_ptr<T> b(new T());
a = b;  // 此后 a 原先所指的对象会被销毁,b 所指的对象引用计数加 1
		//shared_ptr 也可以直接赋值,但是必须是赋给相同类型的 shared_ptr 对象,而不能是普通的 C 指针或 new 运算符的返回值。
		//当共享指针 a 被赋值成 b 的时候,如果 a 原来是 NULL, 那么直接让 a 等于 b 并且让它们指向的东西的引用计数加 1;
		// 如果 a 原来也指向某些东西的时候,如果 a 被赋值成 b, 那么原来 a 指向的东西的引用计数被减 1, 而新指向的对象的引用计数加 1。
6./*已定义的共享指针指向新的new对象————reset()*/
shared_ptr<T> ptr(new T());
ptr.reset(new T()); // 原来所指的对象会被销毁

        (2)make_shared辅助函数创建

std::shared_ptr<int> foo = std::make_shared<int> (10);
3.3.4 shared_ptr 与函数

void cat_by_value( std::shared_ptr<Cat> cat){
 cout << "cat use coout "<< cat.use_cout() << endl; // 2 
}


void cat_by_ref( std::shared_ptr<Cat> &cat){
    // cat.reset(new Cat());  // 先创建新对象,将原先cat对象内容覆盖,然后reset
 cout << "cat use coout "<< cat.use_cout() << endl; // 2 
}

std::shared_ptr<Cat> get_shared_ptr(){
    std::shared_ptr<Cat> cat_p= std::make_shared<Cat>("dd);
}

int main(){

    std::shared_ptr<Cat> c1 =make_shared<Cat>("dd");
    cat_by_value(c1);
    cout << "c1 use coout "<< c1.use_cout() << endl; // 1
    
    cat_by_ref(c1);

     std::shared_ptr<Cat> c_p =get_shared_ptr();
      get_shared_ptr->cat_info();  
}
3.4 shared_ptr 和 unique_ptr转换

3.5 weak_ptr  弱引用指针

  std::weak_ptr 是 C++11 引入的一个智能指针类,用于解决 std::shared_ptr 的循环引用问题。它是一个弱引用指针,不会增加指向对象的引用计数,也不会拥有对象的所有权,因此不会影响对象的生命周期。

std::weak_ptr 主要用于解决以下两个问题:

  1. 循环引用问题:当两个或多个对象相互持有对方的 std::shared_ptr,就会形成循环引用,导致对象无法被正确释放,从而产生内存泄漏。使用 std::weak_ptr 可以打破循环引用,避免内存泄漏的发生。

  2. 避免悬挂指针:当对象的 std::shared_ptr 被释放后,指向该对象的 std::weak_ptr 仍然可以继续存在,但是无法访问对象。因此,使用 std::weak_ptr 可以避免悬挂指针的出现,从而提高程序的稳定性。

        使用 std::weak_ptr 需要配合 std::shared_ptr 使用,通过 std::shared_ptr 对象的 weak_ptr 方法来创建 std::weak_ptr 对象。std::weak_ptr 可以通过 lock 方法获取一个有效的 std::shared_ptr 对象,用于访问所指向的对象,但是需要注意,获取的 std::shared_ptr 可能为空,需要进行有效性检查。

// 产生循环依赖问题 

 

// 使用weak_ptr 

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

深入理解nginx的https sni机制

目录 1. 概述2. 初识sni3. nginx的ssl证书配置指令3.1 ssl_certificate3.2 ssl_certificate_key3.3 ssl_password_file4. nginx源码分析4.1 给ssl上下文的初始化4.2 连接初始化4.3 处理sni回调4.2 动态证书的加载5. 总结阅读姊妹篇: 深入理解nginx的https alpn机制 1. 概述 SN…

FreeRTOS 其它知识点

目录 一、低功耗Tickless模式 1、低功耗Tickless模式的引入 2、Tickless 具体实现 二、空闲任务 1、空闲任务相关知识点 2、钩子函数 3、空闲任务钩子函数 三、使用RTOS的好处 一、低功耗Tickless模式 1、低功耗Tickless模式的引入 FreeRTOS 的系统时钟是由滴答定时器中…

数字孪生与智慧交通的融合发展:推动交通行业数字化转型,构建智慧城市新生态

随着信息技术的快速发展和城市化进程的深入推进&#xff0c;交通行业正面临着前所未有的机遇与挑战。传统的交通管理模式已难以满足日益增长的交通需求&#xff0c;而数字化转型则成为了推动交通行业创新发展的必由之路。数字孪生技术作为一种前沿的信息技术手段&#xff0c;为…

LIS(最长上升子序列, 合唱队形)

最长上升子序列 直接使用动态规划&#xff1a; 这个题目的关键就是在于我们选定一个数&#xff0c;然后利用这个数作为标准和这个数之前的所有数进行比较&#xff0c;如果比前面某一个数要大&#xff0c;那么就需要将这数自己本身的现存的最长长度与比较出来的数的最长加一&am…

【iOS ARKit】RealityKit 同步机制

协作 Session 可以很方便地实现多用户之间的AR体验实时共享&#xff0c;但开发者需要自行负责并确保AR场景的完整性&#xff0c;自行负责虚拟物体的创建与销毁。为简化同步操作&#xff0c;RealityKit 内建了同步机制&#xff0c;RealityKit 同步机制基于 Multipeer Connectivi…

Java核心卷一 · 笔记04

C++ type_info 类的使用 在 C++ 中,type_info 类是一个标准库提供的用于运行时类型信息的类。它定义在 <typeinfo> 头文件中,并用于获取和比较类型信息。下面是一些使用 type_info 类的常见操作示例: 包含头文件:#include <typeinfo>使用 typeid 运算符获取类…

安全防御(第六次作业)

攻击可能只是一个点&#xff0c; 防御需要全方面进行 IAE引擎 DFI和DPI技术 --- 深度检测技术 DPI --- 深度包检测技术 --- 主要针对完整的数据包&#xff08;数据包分片&#xff0c;分段需要重组&#xff09; &#xff0c;之后对 数据包的内容进行识别。&#xff08;应用层&a…

18 SpringMVC实战

18 SpringMVC实战 1. 课程介绍2. Spring Task定时任务1. 课程介绍 2. Spring Task定时任务 package com.imooc.reader.task

LSS 论文及代码详解:Lift, Splat, Shoot:

文章目录 1. 相关概念1.1 什么叫做BEV自底向上方法1.2 BEV网格2. 自底向上方法框架-LSS2.1 视锥点云和Lift操作2.1.1 视锥点云的空间位置2.1.2 视锥点云的特征(Context)2.2 BEV Pillar和Splat操作2.3 Shoot: Motion Planning2.4 完整的pipeline2.5 cumsum_trick(): 池化累积求…

LINUX基础培训二十七之shell标准输入、输出、错误

一、Shell 输入/输出重定向 大多数 UNIX 系统命令从你的终端接受输入并将所产生的输出发送回​​到您的终端。一个命令通常从一个叫标准输入的地方读取输入&#xff0c;默认情况下&#xff0c;这恰好是你的终端。同样&#xff0c;一个命令通常将其输出写入到标准输出&#xff…

数电学习笔记——逻辑代数的基本定理

目录 一、带入定理 二、反演定理 三、对偶定理 一、带入定理 在任何一个包含变量A的逻辑等式中&#xff0c;若以另外一个逻辑式代入式中所有A的位置&#xff0c;则等式仍然成立。 例1&#xff1a;&#xff08;AB&#xff09;AB 将&#xff08;BC&#xff09;带入等式中所…

Jlink Segger工具软件的应用(如何连接)

一、Jlink Commander的如何连接 1、点击打开“Jlink Commander” 2、输入“connect” 3、根据提示输入“&#xff1f;”。 此处是选择MCU 内核类型 4、此时jink commander 会提示选择对应的内核&#xff0c;如“图F5.1”。根据内核类型进行选择。 SWM1xx系列、SWM2xx系列…

做活动和会议直播,为什么要多个媒体平台同步直播?

传媒如春雨&#xff0c;润物细无声&#xff0c;大家好&#xff0c;我是51媒体网胡老师。 多个媒体平台同步直播活动和会议的原因主要有以下几点&#xff1a; 扩大观众覆盖面&#xff1a;不同的媒体平台拥有各自独特的用户群体&#xff0c;通过在多个媒体平台同步直播&#xff…

2024年3月阿里云服务器价格下调折扣表(附优惠价格表)

阿里云服务器ECS等核心产品价格全线下调&#xff0c;最高幅度达55%&#xff0c;2024年3月1日生效&#xff0c;针对ECS部分在售产品的官网折扣价、ECS计算型节省计划进行调整&#xff0c;生效后&#xff0c;基于官网折扣价的新购和续费&#xff0c;将按照新的价格进行计费。阿里…

IDEA-DeBug理论与实践

文章目录 01_Debug简介和意义02_IDEA中的Debug步骤03_跳转到当前代码执行的行04_步过调试的使用05_步入调试的使用06_强制步入调试的使用07_步出调试的使用08_回退断点调试的使用09_运行到光标处10_计算表达式11_条件断点12_多线程调试 在软件开发中&#xff0c;IDEA&#xff0…

物联网技术助力智慧城市安全建设:构建全方位、智能化的安全防护体系

一、引言 随着城市化进程的加速和信息技术的迅猛发展&#xff0c;智慧城市已成为现代城市发展的重要方向。在智慧城市建设中&#xff0c;安全是不可或缺的一环。物联网技术的快速发展为智慧城市安全建设提供了有力支持&#xff0c;通过构建全方位、智能化的安全防护体系&#…

SpringMVC01、回顾MVC

1、回顾MVC 1.1、什么是MVC MVC是模型(Model)、视图(View)、控制器(Controller)的简写&#xff0c;是一种软件设计规范。是将业务逻辑、数据、显示分离的方法来组织代码。MVC主要作用是降低了视图与业务逻辑间的双向偶合。MVC不是一种设计模式&#xff0c;MVC是一种架构模式。…

Qt槽函数不响应的原因总结

Qt专栏&#xff1a;http://t.csdnimg.cn/LE2Lx 目录 1.问题 2.原因 2.1.没有继承QObject&#xff0c;声明Q_OBJECT宏 2.2.信号槽参数不匹配 2.3.信号函数未声明为 signals 2.4.访问权限 2.5.注意connect的位置&#xff0c;信号在创建信号槽连接前使用&#xff0c;则无法…

前端 JS 经典:Content-type 详解

1. 什么是 Content-Type Content-Type 是 HTTP 协议中的一个请求头或响应头字段&#xff0c;用于指示发送或接收的实体的媒体类型&#xff0c;告诉服务器或客户端如何解析和处理请求或响应的主体部分。 2. Content-Type 的构成 Content-Type 由两部分组成&#xff1a;媒体类型…

(python)多线程

前言 Python 多线程的应用场景通常是在需要同时执行多个 I/O 密集型任务时&#xff0c;以提高程序的效率和性能。 多线程应用场景 网络爬虫&#xff1a;当需要从多个网站获取数据时&#xff0c;使用多线程可以同时发起多个 HTTP 请求&#xff0c;以加快数据获取速度。 数据库操…