首页 > 其他分享 >智能指针

智能指针

时间:2024-09-10 22:37:56浏览次数:8  
标签:std 内存 智能 引用 shared ptr 指针

C++ 智能指针解析

为什么需要智能指针

众所周知,Java 和 C/C++ 中间隔着一堵由内存动态分配和垃圾回收机制所围成的墙。

java 大佬们经常吐槽 C++ 没有垃圾回收(Gabage Collector)机制,而 C++ 爱好者也经常攻击 Java 限制太死,不够灵活。

其实 Java 并不是最早实践内存动态分配和垃圾自动回收机制的语言,这个构想在 1960 年就已经在MIT 的教学语言 Lisp 中提出。

在 C/C++ 中最为灵活的工具就是指针了,但指针也是很多噩梦的源头,内存泄露(memory leak)和内存非法访问应该算是 C++ 程序员的家常便饭了。

但是又不能抛弃指针带来的灵活性,不过幸好 C++ 里有了智能指针,虽然在使用上有局限性,但是能够最大程度减少程序员手动管理指针生命周期的负担。

指针的强大很大程度上源于它们能追踪动态分配的内存。通过指针来 管理这部分内存是很多操作的基础,包括一些用来处理复杂数据结构 的操作。要完全利用这些能力,需要理解C的动态内存管理是怎么回 事。

C++是面向内存编程,Java是面向数据结构编程。

C++里,内存是裸露的,可以拿到地址,随意徜徉,增了删了,没人拦你,等到跑的时候再崩给你看。

Java里,能操作的都是设计好的数据结构,array有长度,String不可变,每一个都是安全的,在内存和程序员之间,隔着JVM,像是包住了边边角角的房间,随便小孩折腾,不会受伤。

Java程序员是孩子,嚷嚷要这个那个,玩完了就丢,JVM是家长,买买买,还要负责收拾。有的孩子熊点,屋子很乱,收拾起来费劲,但房子还在。

C++程序员是神,操纵着江河湖海,日月星辰,但能力越大,责任越大,万一新来的神比较愣,手一滑,宇宙就退出了。

新手写C++,像是抱着一捆指针,在浩瀚的内存中裸奔。跑着跑着,有的针掉了,不知踪影,内存就泄露了;跑着跑着,突然被人逮住,按在地上打的error纷飞,内存就越界了;终于到了,舒了口气,把针插在脚下,念出咒语,

“delete”

系统就崩溃了

C/C++ 常见的内存错误

在实际的 C/C++ 开发中,我们经常会遇到诸如 coredump、segmentfault 之类的内存问题,使用指针也会出现各种问题,比如:

  • 野指针:未初始化或已经被释放的指针被称为野指针
  • 空指针:指向空地址的指针被称为空指针
  • 内存泄漏:如果在使用完动态分配的内存后忘记释放,就会造成内存泄漏,长时间运行的程序可能会消耗大量内存。
  • 悬空指针:指向已经释放的内存的指针被称为悬空指针
  • 内存泄漏和悬空指针的混合:在一些情况下,由于内存泄漏和悬空指针共同存在,程序可能会出现异常行为。
  • ...

智能指针

而智能指针是一种可以自动管理内存的指针,它可以在不需要手动释放内存的情况下,确保对象被正确地销毁。

这种指针可以显著降低程序中的内存泄漏和悬空指针的风险。智能指针的核心思想就是 RAII, C++中,智能指针常用的主要是两个类实现:

  • std::unique_ptr
  • std::shared_ptr

std::unique_ptr

std::unique_ptr是一个独占所有权的智能指针,它保证指向的内存只能由一个unique_ptr拥有,不能共享所有权。

当unique_ptr超出作用域时,它所指向的内存会自动释放。

#include <memory>
#include <iostream>

int main() {
    std::unique_ptr<int> ptr(new int(10));
    std::cout << *ptr << std::endl; // 输出10
    // unique_ptr在超出作用域时自动释放所拥有的内存
    return 0;
}

std::shared_ptr

std::shared_ptr是一个共享所有权的智能指针,它允许多个shared_ptr指向同一个对象,当最后一个shared_ptr超出作用域时,所指向的内存才会被自动释放。

#include <memory>
#include <iostream>

int main() {
    std::shared_ptr<int> ptr1(new int(10));
    std::shared_ptr<int> ptr2 = ptr1; // 通过拷贝构造函数创建一个新的shared_ptr,此时引用计数为2
    std::cout << *ptr1 << " " << *ptr2 << std::endl; // 输出10 10
    // ptr2超出作用域时,所指向的内存不会被释放,因为此时ptr1仍然持有对该内存的引用
    return 0;
}

总的来说,智能指针可以提高程序的安全性和可靠性,避免内存泄漏和悬空指针等问题。

但需要注意的是,智能指针不是万能的,也并不是一定要使用的,有些场景下手动管理内存可能更为合适。

深入理解 C++ shared_ptr之手写

正如这篇文章 智能指针 (opens new window)所说,智能指针是一种可以自动管理内存的指针,它可以在不需要手动释放内存的情况下,确保对象被正确地销毁。

可以显著降低程序中的内存泄漏和悬空指针的风险。

而用得比较多的一种智能指针就是 shared_ptr ,从名字也可以看出来,shared 强调分享,也就是指针的所有权不是独占。

shared_ptr 的使用

shared_ptr 的一个关键特性是可以共享所有权,即多个 shared_ptr 可以同时指向并拥有同一个对象。当最后一个拥有该对象的 shared_ptr 被销毁或者释放该对象的所有权时,对象会自动被删除。这种行为通过引用计数实现,即 shared_ptr 有一个成员变量记录有多少个 shared_ptr 共享同一个对象。

#include <iostream>
#include <memory>

class MyClass {
public:
    MyClass() { std::cout << "MyClass 构造函数\n"; }
    ~MyClass() { std::cout << "MyClass 析构函数\n"; }
    void do_something() { std::cout << "MyClass::do_something() 被调用\n"; }
};

int main() {
    {
        std::shared_ptr<MyClass> ptr1 = std::make_shared<MyClass>();
        {
            std::shared_ptr<MyClass> ptr2 = ptr1; // 这里共享 MyClass 对象的所有权
            ptr1->do_something();
            ptr2->do_something();
            std::cout << "ptr1 和 ptr2 作用域结束前的引用计数: " << ptr1.use_count() << std::endl;
        } // 这里 ptr2 被销毁,但是 MyClass 对象不会被删除,因为 ptr1 仍然拥有它的所有权
        std::cout << "ptr1 作用域结束前的引用计数: " << ptr1.use_count() << std::endl;
    } // 这里 ptr1 被销毁,同时 MyClass 对象也会被删除,因为它是最后一个拥有对象所有权的 shared_ptr

    return 0;
}
MyClass 构造函数
MyClass::do_something() 被调用
MyClass::do_something() 被调用
ptr1 和 ptr2 作用域结束前的引用计数: 2
ptr1 作用域结束前的引用计数: 1
MyClass 析构函数

引用计数如何实现的

说起 shared_ptr 大家都知道引用计数,但是问引用计数实现的细节,不少同学就回答不上来了,其实引用计数本身是使用指针实现的,也就是将计数变量存储在堆上,所以共享指针的shared_ptr 就存储一个指向堆内存的指针,文章后面会手动实现一个 shared_ptr。

shared_ptr 的 double free 问题

double free 问题就是一块内存空间或者资源被释放两次。

那么为什么会释放两次呢?

double free 可能是下面这些原因造成的:

  • 直接使用原始指针创建多个 shared_ptr,而没有使用 shared_ptr 的 make_shared 工厂函数,从而导致多个独立的引用计数。
  • 循环引用,即两个或多个 shared_ptr 互相引用,导致引用计数永远无法降为零,从而无法释放内存。

如何解决 double free

解决 shared_ptr double free 问题的方法:

  • 使用 make_shared 函数创建 shared_ptr 实例,而不是直接使用原始指针。这样可以确保所有 shared_ptr 实例共享相同的引用计数。
  • 对于可能产生循环引用的情况,使用 weak_ptr。weak_ptr 是一种不控制对象生命周期的智能指针,它只观察对象,而不增加引用计数。这可以避免循环引用导致的内存泄漏问题。
#include <iostream>
#include <memory>
class B; // 前向声明
class A {
public:
    std::shared_ptr<B> b_ptr;
    ~A() {
        std::cout << "A destructor called" << std::endl;
    }
};

class B {
public:
    std::shared_ptr<A> a_ptr;
    ~B() {
        std::cout << "B destructor called" << std::endl;
    }
};

int main() {
    {
        std::shared_ptr<A> a = std::make_shared<A>();
        std::shared_ptr<B> b = std::make_shared<B>();
        a->b_ptr = b; // A 指向 B
        b->a_ptr = a; // B 指向 A
    } // a 和 b 离开作用域,但由于循环引用,它们的析构函数不会被调用

    std::cout << "End of main" << std::endl;
    return 0;
}

上面这种循环引用问题可以使用std::weak_ptr来避免循环引用。

std::weak_ptr不会增加所指向对象的引用计数,因此不会导致循环引用。

下面

标签:std,内存,智能,引用,shared,ptr,指针
From: https://www.cnblogs.com/sfbslover/p/18407363

相关文章

  • 指针
    内存就是计算机的存储空间,用于存储程序的指令、数据和状态。在C语言中,内存被组织成一系列的字节,每个字节都有一个唯一的地址。程序中的变量和数据结构存储在这些字节中。根据变量的类型和作用域,内存分为几个区域,如栈(stack)、堆(heap)和全局/静态存储区。内存编址计算机的内存是一......
  • LeetCode算法—双指针
    一:普通双指针1、题目1:两数求和-1(1)方法1:普通双指针思路:定义两个指针;第一个指针放在数组的首位;第二个指针放在下一个元素的位置;然后遍历这个;如果两个元素的和为目标值就返回对对应的下标和索引值!deffuc(nums,target):foriinrange(len(nums)):forjinrange(i......
  • 【自用21.】C++-this指针
    Human::Human(intage,intsalary){ cout<<"调用自定义的构造函数"<<endl; this->age=age;//this是一个特殊的指针,指向这个对象本身 this->salary=salary; name="无名"; addr=newchar[64]; strcpy_s(addr,64,"China");}......
  • 《ChatGPT:强大的人工智能聊天机器人》
    一、引言在当今科技飞速发展的时代,人工智能已经成为了各个领域的热门话题。其中,聊天机器人作为人工智能的一个重要应用,正在逐渐改变人们的生活和工作方式。ChatGPT作为一款强大的人工智能聊天机器人,以其出色的语言理解和生成能力,受到了广泛的关注和应用。本文将对ChatGPT......
  • 现代科技智能问答助手
    一.部署背景为了确保现代科技智能问答助手的性能和可靠性,我们选择在阿里云的英特尔至强可扩展处理器的G8i云环境实例上进行部署。G8i实例提供高性能计算能力,适用于需要大量计算资源和高并发处理能力的应用场景。二.部署目标-确保问答助手的高响应速度和处理能力。-提供稳定的服......
  • 房屋租赁服务智能化:Spring Boot 系统设计
    第2章技术介绍2.1相关技术房屋租赁系统是在JSP+MySQL开发环境的基础上开发的。JSP是一种服务器端脚本语言,易于学习,实用且面向用户。全球超过35%的JSP驱动的互联网站点使用JSP。MySQL是一个数据库管理系统,因为它的体积小但速度快,成本低,或者开源受到中小型网站的青睐。因......
  • 小董小记——9.10教师节人工智能教育技术学小记(1)
    下午蒙蒙小雨,也抵挡不住我们开拓知识技能的热情。我们也算是使用博客园的初学者,也在逐步学会通过使用博客园发布自己的一些小随笔,生活学习的小记录。说实话,我在没使用过博客园之前,都是从电视剧里面或者别人口中听来的,所以,我一直以为它是微博的一个分身。结果,并不是。多学习一项技......
  • 参加文心智能体AI大师工坊,成为“AI头号玩家”!
    文心智能体AI大师工坊,万元奖金池“悬赏”!招募”AI头号玩家”,冲击TOP智能体!旅游类智能体、恐怖类游戏智能体、购物类智能体、情感类智能体四大赛题任您选择大赛期间内,完成任一赛题智能体开发,撰写开发心得并提交智能体信息,通过审核即可领取51CTO博客精美礼品!活动介绍及时间赛题阶段赛......
  • 谷歌浏览器在智能手机上的隐私保护措施
    在数字化快速发展的今天,个人信息的安全变得极其重要。智能手机用户通过谷歌浏览器上网时,面临着各种隐私风险。为了确保您的数据安全,本教程将详细介绍如何在手机端使用谷歌浏览器时,通过设置增强隐私保护。(本文由https://chrome.cmrrs.com/站点的作者进行编写,转载时请进行标注。)......
  • 九月十号人工智能
    一.搜索引擎1.引擎分为两种第一种:目录式分类搜索引擎。过程比较复杂,不容易找到想要的信息。第二种:全文检索搜索引擎(关键词搜索)。准确率比较高,信息易于提取2.搜索指令使用filetype指令可以查询特定格式的文件,比如doc\txt\ppt\pdf,搜索格式为:关健词:空格+filetype-+文件格式使用......