【C/C++】内存管理详解:从new/delete到智能指针的全面解析

文章目录

    • 更多文章
    • C/C++中的传统内存管理方式
      • `new`和`delete`运算符
      • `malloc`和`free`函数
      • 传统内存管理的弊端
    • 智能指针的崛起
      • 智能指针的定义与作用
      • C++11引入的标准智能指针
    • 详解C++标准智能指针
      • `std::unique_ptr`
        • 特点
        • 使用方法
        • 适用场景
      • `std::shared_ptr`
        • 特点
        • 使用方法
        • 适用场景
      • `std::weak_ptr`
        • 特点
        • 使用方法
        • 适用场景
    • 智能指针与传统内存管理的比较
      • 内存安全性
      • 性能考量
      • 使用复杂度
    • 智能指针的最佳实践
      • 选择合适的智能指针类型
      • 避免循环引用
      • 与传统指针的混用
      • 避免不必要的拷贝
      • 使用`make_*`函数
    • 案例教程:使用智能指针管理内存
      • 传统方法实现
      • 使用`std::unique_ptr`重构
      • 使用`std::shared_ptr`的场景
    • 更多文章
  • 结语

更多文章

【OpenAI】获取OpenAI API Key的多种方式全攻略:从入门到精通,再到详解教程!!

【VScode】VSCode中的智能编程利器,全面揭秘ChatMoss & ChatGPT中文版

【体验最新的GPT系列模型!支持Open API调用、自定义助手、文件上传等强大功能,助您提升工作效率!点击链接体验:CodeMoss & ChatGPT-AI中文版】
在这里插入图片描述

C/C++中的传统内存管理方式

在深入探讨智能指针之前,我们首先需要了解C/C++中传统的内存管理方式,以便更好地理解智能指针的优势所在。

newdelete运算符

在C++中,newdelete是用于动态分配和释放内存的运算符。

// 使用new分配内存
int* ptr = new int;

// 使用delete释放内存
delete ptr;

通过new,程序员可以在堆上动态分配内存,而通过delete则可以手动释放这部分内存。然而,这种手动管理方式容易导致内存泄漏或悬挂指针等问题。

mallocfree函数

在C语言中,mallocfree函数用于动态内存分配和释放。

// 使用malloc分配内存
int* ptr = (int*)malloc(sizeof(int));

// 使用free释放内存
free(ptr);

malloc函数返回一个void*指针,需要通过类型转换来使用。与new/delete类似,mallocfree也需要开发者手动管理内存,同样面临内存泄漏和其他相关问题。

传统内存管理的弊端

虽然new/deletemalloc/free为程序员提供了灵活的内存管理方式,但其手动管理的特性也带来了诸多弊端:

  1. 内存泄漏:如果开发者忘记调用delete或者free释放内存,程序会出现内存泄漏,长时间运行后可能导致系统资源耗尽。
  2. 悬挂指针:在释放内存后,指针仍然指向原来的地址,如果再次访问可能导致未定义行为。
  3. 异常安全性差:在异常发生时,手动管理的内存释放往往难以保证,容易导致资源泄漏。
  4. 代码复杂度高:需要在多个地方进行内存分配和释放,增加了代码的复杂性和维护难度。

这些问题不仅影响程序的稳定性和性能,还增加了开发和调试的难度。因此,寻求一种更安全、更高效的内存管理方式成为现代C++开发的重要课题。

智能指针的崛起

为了应对传统内存管理方式的诸多问题,C++11标准引入了智能指针,作为一种RAII机制,旨在自动管理内存资源,减少内存泄漏和其他相关问题的发生。
在这里插入图片描述

智能指针的定义与作用

智能指针是一种封装了普通指针的类,通过自动管理内存的分配和释放,简化了内存管理的过程。它们利用独占或共享所有权的概念,确保在对象不再使用时,自动释放相关资源,从而提高代码的安全性和可维护性。

智能指针的主要作用包括:

  1. 自动内存管理:避免手动调用deletefree,减少内存泄漏的风险。
  2. 异常安全:在异常发生时,智能指针能够确保资源被正确释放。
  3. 所有权管理:通过不同类型的智能指针,管理资源的独占或共享所有权,提高代码的表达力和安全性。

C++11引入的标准智能指针

C++11标准引入了三种主要的标准智能指针,分别适用于不同的使用场景:

  1. std::unique_ptr:独占所有权,不能被复制,适用于资源的独占管理。
  2. std::shared_ptr:共享所有权,多个shared_ptr可以指向同一资源,通过引用计数管理资源的生命周期。
  3. std::weak_ptr:观察者指针,不增加引用计数,主要用于解决shared_ptr之间的循环引用问题。

这些智能指针的引入极大地简化了内存管理过程,提升了代码的安全性和可维护性。

详解C++标准智能指针

为了全面掌握智能指针的使用,以下将对C++11标准中的三种主要智能指针进行详细解析,包括其特点、使用方法及适用场景。

std::unique_ptr

特点
  • 独占所有权unique_ptr拥有其所指向资源的独占所有权,不能被复制,只能被移动。
  • 轻量级:相比shared_ptrunique_ptr更为轻量,适用于简单的资源管理场景。
  • 自动释放:当unique_ptr生命周期结束时,自动调用delete释放资源,避免内存泄漏。
使用方法
#include <memory>

void uniquePtrExample() {
    // 创建一个unique_ptr
    std::unique_ptr<int> ptr(new int(10));

    // 访问指针
    std::cout << "Value: " << *ptr << std::endl;

    // 转移所有权
    std::unique_ptr<int> ptr2 = std::move(ptr);

    if (!ptr) {
        std::cout << "ptr is now null." << std::endl;
    }
}
适用场景
  • 资源的独占管理:适用于资源不需要被多个对象共享的场景,如单一对象的内部资源。
  • RAII模式:在资源管理的RAII模式中,unique_ptr是首选工具。
  • 性能要求高的场景:由于unique_ptr无引用计数,适用于对性能有严格要求的场景。

【体验最新的GPT系列模型!支持Open API调用、自定义助手、文件上传等强大功能,助您提升工作效率!点击链接体验:CodeMoss & ChatGPT-AI中文版】
在这里插入图片描述

std::shared_ptr

特点
  • 共享所有权:多个shared_ptr可以共同指向同一资源,通过内部的引用计数机制管理资源的生命周期。
  • 引用计数机制:每一个shared_ptr的拷贝都会增加引用计数,销毁时会减少引用计数,当引用计数为零时,自动释放资源。
  • 灵活性高:适用于多个对象需要共同管理同一资源的场景。
使用方法
#include <memory>

void sharedPtrExample() {
    // 创建一个shared_ptr
    std::shared_ptr<int> ptr1 = std::make_shared<int>(20);

    {
        // 复制shared_ptr
        std::shared_ptr<int> ptr2 = ptr1;
        std::cout << "Value: " << *ptr2 << ", Use count: " << ptr1.use_count() << std::endl;
    }

    // ptr2超出作用域,引用计数减少
    std::cout << "Use count after ptr2 is destroyed: " << ptr1.use_count() << std::endl;
}
适用场景
  • 资源需要被多个对象共享:如共享的数据缓冲区、共享的资源池等。
  • 复杂的数据结构:如图结构、循环引用的场景(需要结合weak_ptr使用)。
  • 需要控制资源的生命周期:当资源的生命周期需要被多个部分共同管理时。

std::weak_ptr

特点
  • 非拥有性观察者指针weak_ptr不拥有资源的所有权,不影响引用计数。
  • 防止循环引用:在shared_ptr间可能产生的循环引用场景中,通过weak_ptr打破循环,避免内存泄漏。
  • 访问资源需转换:要访问资源,需先将weak_ptr转换为shared_ptr,确保资源在访问期间不会被释放。
使用方法
#include <memory>

struct Node {
    std::shared_ptr<Node> next;
    std::weak_ptr<Node> prev; // 使用weak_ptr避免循环引用
};

void weakPtrExample() {
    auto first = std::make_shared<Node>();
    auto second = std::make_shared<Node>();

    first->next = second;
    second->prev = first; // weak_ptr,不增加引用计数
}
适用场景
  • 打破循环引用:在涉及双向引用的数据结构中,如双向链表、图等。
  • 缓存系统:实现缓存时,weak_ptr可用于观察但不拥有缓存对象。
  • 临时访问:在需要临时访问资源但不希望延长其生命周期时。

智能指针与传统内存管理的比较

在了解了传统内存管理方式和智能指针的基本概念后,接下来将具体比较二者在内存安全性、性能和使用复杂度等方面的区别,为开发者选择合适的内存管理策略提供参考。

【体验最新的GPT系列模型!支持Open API调用、自定义助手、文件上传等强大功能,助您提升工作效率!点击链接体验:CodeMoss & ChatGPT-AI中文版】
在这里插入图片描述

内存安全性

传统方法

  • 手动管理内存,容易出现内存泄漏、悬挂指针等问题。
  • 开发者需要严格遵循内存分配和释放的规范,增加了出错的可能性。

智能指针

  • 自动管理内存,减少了人为忘记释放内存的风险。
  • 通过RAII机制,在对象生命周期结束时自动释放资源,提高了内存安全性。
  • weak_ptr有效防止了shared_ptr的循环引用问题。

性能考量

传统方法

  • 无额外的性能开销,适用于对性能有极高要求的场景。
  • 但由于手动管理,错误释放内存可能导致不可预测的性能问题。

智能指针

  • unique_ptr几乎无额外开销,适用于性能敏感的场景。
  • shared_ptr由于引用计数机制,存在一定的性能开销,尤其是在高频率的拷贝和销毁操作中。
  • weak_ptr本身开销较低,但在转换为shared_ptr时需要一定的计算。

使用复杂度

传统方法

  • 灵活性高,但需要开发者手动管理,增加了代码的复杂性和出错概率。
  • 在复杂的应用场景中,维护手动管理的代码较为困难。

智能指针

  • 提供了更高层次的抽象,简化了内存管理的流程。
  • 学习曲线相对较低,但需要理解不同智能指针的适用场景和使用方法。
  • 提高了代码的可读性和可维护性,减少了内存管理相关的出错概率。

智能指针的最佳实践

为了充分发挥智能指针的优势,开发者需要遵循一些最佳实践,合理选择和使用智能指针,避免潜在的问题。

选择合适的智能指针类型

  • std::unique_ptr:当资源拥有权是独占的,且不需要共享时,优先使用unique_ptr。它轻量且效率高,适合大多数独占资源的管理场景。

    std::unique_ptr<MyClass> ptr = std::make_unique<MyClass>();
    
  • std::shared_ptr:当资源需要被多个对象共享时,使用shared_ptr。确保没有不必要的共享所有权,以避免引用计数的额外开销。

    std::shared_ptr<MyClass> ptr1 = std::make_shared<MyClass>();
    std::shared_ptr<MyClass> ptr2 = ptr1;
    
  • std::weak_ptr:在需要观察但不拥有资源的场景中使用,特别是在打破shared_ptr循环引用时。

    std::weak_ptr<MyClass> weakPtr = sharedPtr;
    

避免循环引用

在某些场景,如树形结构、双向链表等,shared_ptr可能导致循环引用,进而引发内存泄漏。此时,应结合weak_ptr使用,打破循环引用。

struct Parent;
struct Child;

struct Parent {
    std::shared_ptr<Child> child;
};

struct Child {
    std::weak_ptr<Parent> parent; // 使用weak_ptr避免循环引用
};

与传统指针的混用

尽可能避免混用智能指针与原始指针,尤其是在管理同一资源时。若确实需要使用原始指针,确保它们仅作为观察者存在,不参与所有权管理。

std::unique_ptr<MyClass> ptr = std::make_unique<MyClass>();
MyClass* rawPtr = ptr.get(); // 仅作为观察者使用

避免不必要的拷贝

对于shared_ptr,避免不必要的拷贝操作,尤其是在高频率的函数调用中,因为每一次拷贝都会增加和减少引用计数,带来性能开销。

// 不推荐
void function(std::shared_ptr<MyClass> ptr);

// 推荐
void function(const std::shared_ptr<MyClass>& ptr);

使用make_*函数

优先使用std::make_uniquestd::make_shared等工厂函数创建智能指针,避免手动使用new,提高代码的安全性和可读性。

auto ptr = std::make_unique<MyClass>();
auto sptr = std::make_shared<MyClass>();

案例教程:使用智能指针管理内存

通过一个实际案例,展示传统内存管理方式与智能指针的应用差异,帮助读者直观理解智能指针的优势。

传统方法实现

假设我们需要实现一个简单的类Person,并在主函数中动态创建和管理Person对象。

#include <iostream>
#include <string>

class Person {
public:
    Person(const std::string& name) : name_(name) {
        std::cout << "Person " << name_ << " created." << std::endl;
    }

    ~Person() {
        std::cout << "Person " << name_ << " destroyed." << std::endl;
    }

    void greet() const {
        std::cout << "Hello, I am " << name_ << "." << std::endl;
    }

private:
    std::string name_;
};

int main() {
    // 动态分配Person对象
    Person* person = new Person("Alice");
    person->greet();

    // 忘记释放内存,导致内存泄漏
    // delete person;

    return 0;
}

问题

  • 如果忘记调用delete person;,会导致内存泄漏。
  • 在异常发生时,delete可能无法被调用,进一步加剧内存泄漏的问题。

使用std::unique_ptr重构

通过使用std::unique_ptr,自动管理Person对象的生命周期,避免内存泄漏。

#include <iostream>
#include <string>
#include <memory>

class Person {
public:
    Person(const std::string& name) : name_(name) {
        std::cout << "Person " << name_ << " created." << std::endl;
    }

    ~Person() {
        std::cout << "Person " << name_ << " destroyed." << std::endl;
    }

    void greet() const {
        std::cout << "Hello, I am " << name_ << "." << std::endl;
    }

private:
    std::string name_;
};

int main() {
    {
        // 使用unique_ptr管理Person对象
        std::unique_ptr<Person> person = std::make_unique<Person>("Bob");
        person->greet();
    } // person超出作用域,自动调用delete

    return 0;
}

优势

  • 自动释放内存,无需手动调用delete
  • 即使在异常发生时,unique_ptr也能确保资源被正确释放。

使用std::shared_ptr的场景

假设我们有多个对象需要共享同一个Person对象,使用std::shared_ptr可以方便地管理共享所有权。

#include <iostream>
#include <string>
#include <memory>

class Person {
public:
    Person(const std::string& name) : name_(name) {
        std::cout << "Person " << name_ << " created." << std::endl;
    }

    ~Person() {
        std::cout << "Person " << name_ << " destroyed." << std::endl;
    }

    void greet() const {
        std::cout << "Hello, I am " << name_ << "." << std::endl;
    }

private:
    std::string name_;
};

void greetPerson(std::shared_ptr<Person> person) {
    person->greet();
}

int main() {
    // 使用shared_ptr管理Person对象
    std::shared_ptr<Person> person = std::make_shared<Person>("Charlie");
    greetPerson(person);
    std::cout << "Use count: " << person.use_count() << std::endl;

    return 0;
}

优势

  • 通过shared_ptr,多个函数或对象可以共享同一个Person对象,而无需担心内存泄漏。
  • 当所有shared_ptr实例被销毁时,资源自动释放。

更多文章

【OpenAI】获取OpenAI API Key的多种方式全攻略:从入门到精通,再到详解教程!!

【VScode】VSCode中的智能编程利器,全面揭秘ChatMoss & ChatGPT中文版

结语

掌握内存管理不仅是C/C++开发者的必备技能,更是提升编程能力的重要一步。通过理解传统方法与智能指针的优劣,并灵活运用智能指针的各类工具,开发者能够编写出更加健壮、高效的代码,轻松应对复杂的开发挑战。希望本篇文章能够为你在内存管理的道路上提供有力的指导,助你在C/C++编程的世界中游刃有余。

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

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

相关文章

Python实现2048小游戏

2048是一个单人益智游戏&#xff0c;目标是移动和合并数字&#xff0c;以达到2048。 1. 实现效果 Python实现2048小游戏 2. 游戏规则 简单地理解一下规则 基本规则&#xff1a; 4x4棋盘&#xff0c;每个格可包含一个2的倍数的数字&#xff0c;初始时为空&#xff0c;表示0。…

Wireshark常用功能使用说明

此处用于记录下本人所使用 wireshark 所可能用到的小技巧。Wireshark是一款强大的数据包分析工具&#xff0c;此处仅介绍常用功能。 Wireshark常用功能使用说明 1.相关介绍1.1.工具栏功能介绍1.1.1.时间戳/分组列表概况等设置 1.2.Windows抓包 2.wireshark过滤器规则2.1.wiresh…

像素流送api ue多人访问需要什么显卡服务器

关于像素流送UE推流&#xff0c;在之前的文章里其实小芹和大家聊过很多&#xff0c;不过今天偶然搜索发现还是有很多小伙伴&#xff0c;在搜索像素流送相关的问题&#xff0c;搜索引擎给的提示有这些。当然这些都是比较短的词汇&#xff0c;可能每个人真正遇到的问题和想获取的…

基于Vue3+Element Plus 实现多表单校验

使用场景 表单校验在日常的开发需求中是一种很常见的需求&#xff0c;通常在提交表单发起请求前校验用户输入是否符合规则&#xff0c;通常只需formRef.value.validate()即可校验&#xff0c;但是&#xff0c;例如一些多步骤表单、动态表单、以及不同的用户角色可能看到不同的表…

ONVIF协议网络摄像机客户端使用gsoap获取RTSP流地址GStreamer拉流播放

什么是ONVIF协议 ONVIF&#xff08;开放式网络视频接口论坛&#xff09;是一个全球性的开放式行业论坛&#xff0c;旨在促进开发和使用基于物理IP的安全产品接口的全球开放标准。 ONVIF规范的目标是建立一个网络视频框架协议&#xff0c;使不同厂商生产的网络视频产品完全互通。…

【Datawhale组队学习】模型减肥秘籍:模型压缩技术6——项目实践

NNI (Neural Network Intelligence) 是由微软开发的一个开源自动化机器学习&#xff08;AutoML&#xff09;库&#xff0c;用于帮助研究人员和开发人员高效地进行机器学习实验。它提供了一套丰富的工具来进行模型调优、神经网络架构搜索、模型压缩以及自动化的超参数搜索。 1…

通讯专题4.1——CAN通信之计算机网络与现场总线

从通讯专题4开始&#xff0c;来学习CAN总线的内容。 为了更好的学习CAN&#xff0c;先从计算机网络与现场总线开始了解。 1 计算机网络体系的结构 在我们生活当中&#xff0c;有许多的网络&#xff0c;如交通网&#xff08;铁路、公路等&#xff09;、通信网&#xff08;电信、…

【51单片机】程序实验910.直流电机-步进电机

主要参考学习资料&#xff1a;B站【普中官方】51单片机手把手教学视频 前置知识&#xff1a;C语言 单片机套装&#xff1a;普中STC51单片机开发板A4标准版套餐7 码字不易&#xff0c;求点赞收藏加关注(•ω•̥) 有问题欢迎评论区讨论~ 目录 程序实验9&10.直流电机-步进电机…

Qt支持RKMPP硬解的视频监控系统/性能卓越界面精美/实时性好延迟低/录像存储和回放/云台控制

一、前言 之前做的监控系统&#xff0c;已经实现了在windows上硬解码比如dxva2和d3d11va&#xff0c;后续又增加了linux上的硬解vdpau的支持&#xff0c;这几种方式都是跨系统的硬解实现方案&#xff0c;也是就是如果都是windows系统&#xff0c;无论X86还是ARM都通用&#xf…

Web API基本认知

作用和分类 作用&#xff1a;就是使用JS去操作html和浏览器 分类&#xff1a;DOM&#xff08;文档对象模型&#xff09;、BOM&#xff08;浏览器对象模型&#xff09; 什么是DOM DOM&#xff08;Document Object Model ——文档对象模型&#xff09;是用来呈现以及与任意 HTM…

Linux——自定义简单shell

shell 自定义shell目标普通命令和内建命令&#xff08;补充&#xff09; shell实现实现原理实现代码 自定义shell 目标 能处理普通命令能处理内建命令要能帮助我们理解内建命令/本地变量/环境变量这些概念理解shell的运行 普通命令和内建命令&#xff08;补充&#xff09; …

智能桥梁安全运行监测系统守护桥梁安全卫士

一、方案背景 桥梁作为交通基础设施中不可或缺的重要组成部分&#xff0c;其安全稳定的运行直接关联到广大人民群众的生命财产安全以及整个社会的稳定与和谐。桥梁不仅是连接两地的通道&#xff0c;更是经济发展和社会进步的重要纽带。为了确保桥梁的安全运行&#xff0c;桥梁安…

【Python网络爬虫笔记】5-(Request 带参数的get请求) 爬取豆瓣电影排行信息

目录 1.抓包工具查看网站信息2.代码实现3.运行结果 1.抓包工具查看网站信息 请求路径 url:https://movie.douban.com/typerank请求参数 页面往下拉&#xff0c;出现新的请求结果&#xff0c;参数start更新&#xff0c;每次刷新出20条新的电影数据 2.代码实现 # 使用网络爬…

新质驱动·科东软件受邀出席2024智能网联+低空经济暨第二届湾区汽车T9+N闭门会议

为推进广东省加快发展新质生产力&#xff0c;贯彻落实“百县千镇万村高质量发展工程”&#xff0c;推动韶关市新丰县智能网联新能源汽车、低空经济与数字技术的创新与发展&#xff0c;充分发挥湾区汽车产业链头部企业的带动作用。韶关市指导、珠三角湾区智能网联新能源汽车产业…

C#使用ExcelDataReader读取Xlsx文件为DataTable对象

创建控制台项目 在NuGet中安装ExcelDataReader.DataSet 3.7.0 创建一个xlsx文件 测试代码 读取xlsx文件内容&#xff0c;为一个DataTable对象。 读取xlsx时&#xff0c;xlsx文件不能被其他软件打开&#xff0c;否则会报“进程无法访问此文件”的错。 using ExcelDataRead…

“harmony”整合不同平台的单细胞数据之旅

其实在Seurat v3官方网站的Vignettes中就曾见过该算法&#xff0c;但并没有太多关注&#xff0c;直到看了北大张泽民团队在2019年10月31日发表于Cell的《Landscap and Dynamics of Single Immune Cells in Hepatocellular Carcinoma》&#xff0c;为了同时整合两类数据&#xf…

智慧银行反欺诈大数据管控平台方案(一)

智慧银行反欺诈大数据管控平台建设方案的核心在于通过整合先进的大数据技术和深度学习算法&#xff0c;打造一个全面、智能且实时的反欺诈系统&#xff0c;以有效识别、预防和应对各类金融欺诈行为。该方案涵盖数据采集、存储、处理和分析的全流程&#xff0c;利用多元化的数据…

基于 JNI + Rust 实现一种高性能 Excel 导出方案(上篇)

每个不曾起舞的日子,都是对生命的辜负。 ——尼采 一、背景:Web 导出 Excel 的场景 Web 导出 Excel 功能在数据处理、分析和共享方面提供了极大的便利,是许多 Web 应用程序中的重要功能。以下是一些典型的场景: 数据报表导出:在企业管理系统(如ERP、CRM)中,用户经常需…

使用 Tkinter 创建一个简单的 GUI 应用程序来合并视频和音频文件

使用 Tkinter 创建一个简单的 GUI 应用程序来合并视频和音频文件 Python 是一门强大的编程语言&#xff0c;它不仅可以用于数据处理、自动化脚本&#xff0c;还可以用于创建图形用户界面 (GUI) 应用程序。在本教程中&#xff0c;我们将使用 Python 的标准库模块 tkinter 创建一…

「Mac畅玩鸿蒙与硬件35」UI互动应用篇12 - 简易日历

本篇将带你实现一个简易日历应用&#xff0c;显示当前月份的日期&#xff0c;并支持选择特定日期的功能。用户可以通过点击日期高亮选中&#xff0c;还可以切换上下月份&#xff0c;体验动态界面的交互效果。 关键词 UI互动应用简易日历动态界面状态管理用户交互 一、功能说明…