【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_ptrstd::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_ptrstd::shared_ptr<int> ptr1 = std::make_shared<int>(20);{// 复制shared_ptrstd::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超出作用域,自动调用deletereturn 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++编程的世界中游刃有余。

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

MATLAB - ROS2 ros2genmsg 生成自定义消息(msg/srv...)

系列文章目录 前言 语法 ros2genmsg(folderpath)ros2genmsg(folderpath,Name=Value) 一、说明 ros2genmsg(folderpath) 通过读取指定文件夹路径下的 ROS 2 自定义信息和服务定义来生成 ROS 2 自定义信息。函数文件夹必须包含一个或多个 ROS 2 软件包。这些软件包包含 .msg 文…

Vue进阶之单组件开发与组件通信

书接上篇&#xff0c;我们了解了如何快速创建一个脚手架&#xff0c;现在我们来学习如何基于vite创建属于自己的脚手架。在创建一个新的组件时&#xff0c;要在新建文件夹中打开终端创建一个基本的脚手架&#xff0c;可在脚手架中原有的文件中修改或在相应路径重新创建&#xf…

深度学习的python基础(1)

一.tensor创建 1.张量的定义 张量在形式上就是多维数组&#xff0c;例如标量就是0维张量&#xff0c;向量就是一维张量&#xff0c;矩阵就是二维张量&#xff0c;而三维张量就可以想象RGB图片&#xff0c;每个channel是一个二维的矩阵&#xff0c;共有三个channel&#xff0…

scss文件内引入其他scss文件报错

在 Sass (SCSS) 中&#xff0c;import 语句用于在当前文件中导入其他 Sass 文件&#xff0c;以便你可以重用样式和变量等。然而&#xff0c;从 Dart Sass 1.23.0 版本开始&#xff0c;import 语句已经被标记为弃用&#xff08;deprecated&#xff09;&#xff0c;并计划在未来的…

Day28 贪心算法 part02

122.买卖股票的最佳时机II 本题解法很巧妙,本题大家可以先自己思考一下然后再看题解,会有惊喜! class Solution {public int maxProfit(int[] prices) {//分析每一天的情况。只要保证今天买,明天卖可以不亏钱,那就是最大的利润。把每一天可以赚钱的机会都不放过,先把能挣…

Unity3D模型场景等测量长度和角度功能demo开发

最近项目用到多段连续测量物体长度和角度功能&#xff0c;自己研究了下。 1.其中向量角度计算&#xff1a; 需要传入三个坐标来进行计算。三个坐标确定两条向量线段的方向&#xff0c;从而来计算夹角。 public Vector3 SetAngle(Vector3 p1, Vector3 p2,Vector3 p3) { …

【MATLAB】基于RSSI的蓝牙定位与例程,设置4个基站、二维定位

目录 ​编辑 商品描述 主要功能 技术细节 适用场景 下载链接 商品描述 这款基于接收信号强度指示&#xff08;RSSI&#xff09;原理的蓝牙定位程序&#xff0c;专为需要高效、可靠定位解决方案的开发者和研究人员设计。它能够在二维平面内&#xff0c;通过4个锚点实现对未…

npm 最新国内淘宝镜像地址源 (旧版已不能用)

注意&#xff1a;原域名https://registry.npm.taobao.org/ 在 2022.06.30 号正式下线和停止 DNS 解析 最新地址&#xff1a; #最新地址 淘宝 NPM 镜像站喊你切换新域名啦! npm config set registry https://registry.npmmirror.com 查看镜像使用状态 npm config get registr…

【学习笔记】基于RTOS的设计中的堆栈溢出(Stack Overflow)-第1部分

本文由RTOS专家Jean J. Labrosse撰写。 基于RTOS的应用程序中的每个任务都需要自己的堆栈,堆栈的大小取决于任务的要求(例如,函数调用嵌套、传递给函数的参数、局部变量等)。 为了避免堆栈溢出,开发人员需要过度分配堆栈空间,但不要太多,以避免浪费RAM。 什么是堆栈溢…

单链表---移除链表元素

对于无头单向不循环链表&#xff0c;给出头结点head与数值val&#xff0c;删除链表中数据值val的所有结点 #define ListNodeDataType val struct ListNode { struct ListNode* psll;ListNodeDataType val; } 方法一---遍历删除 移除所有数值为val的链表结点&#xff0c;…

ROC曲线

文章目录 前言一、ROC的应用&#xff1f;二、使用方式1. 数据准备2.绘图可视化 前言 在差异分析中&#xff0c;ROC曲线可以用来评估不同组之间的分类性能差异。差异分析旨在比较不同组之间的特征差异&#xff0c;例如在基因表达研究中比较不同基因在不同条件或组织中的表达水平…

基数排序(代码+注释)

#include <stdio.h> #include <stdlib.h>// 获取数组中的最大值 int GetMax(int* a, int n) {int max a[0];for (int i 1; i < n; i) {if (a[i] > max) {max a[i];}}return max; }// 对数组按照某个位数进行计数排序 void CountingSortForRadix(int* a, i…

10)语法分析:介词辨析

1. For 主要用法&#xff1a; 目的或原因&#xff1a;表示做某事的目的或原因。 Example: I am studying for the exam. (我在为考试学习。)Example: She bought a gift for her friend. (她为她的朋友买了一份礼物。) 持续时间&#xff1a;表示某个动作或状态的持续时间。 Ex…

第一届“吾杯”网络安全技能大赛 Writeup

战队信息 战队名称&#xff1a;在你眼中我是誰&#xff0c;你想我代替誰&#xff1f; 战队排名&#xff1a;13 Misc Sign Hex 转 Str&#xff0c;即可得到flag。 原神启动&#xff01; 不好评价&#xff0c;stegsolve 秒了&#xff1a; WuCup{7c16e21c-31c2-439e-a814-b…

AJAX一、axios使用,url组成(协议,域名,资源路径)查询参数和化简,错误处理,请求/响应报文,状态码,接口文档,

一、AJAX是什么 概念 &#xff1a; AJAX是一种与服务器&#xff08;后端&#xff09;通信的技术 二、请求库axios的基本用法 1导包 2使用 // 1. 发请求 axios({ url: 请求地址 }).then(res > { // 2.接收并使用数据 }) <body><p class"province"…

深入学习MapReduce:原理解析与基础实战

标题&#xff1a;深入学习MapReduce&#xff1a;原理解析与基础实战 MapReduce是一种分布式计算框架&#xff0c;用于大规模数据的处理和分析。作为Hadoop生态系统的核心组件&#xff0c;MapReduce凭借其简单的编程模型和强大的并行计算能力&#xff0c;广泛应用于大数据领域。…

鸿蒙HarmonyOS学习笔记(6)

定义扩展组件样式&#xff1a;Extend装饰器 在前文的示例中&#xff0c;可以使用Styles用于样式的重用&#xff0c;在Styles的基础上&#xff0c;我们提供了Extend&#xff0c;用于扩展原生组件样式。 说明 从API version 9开始&#xff0c;该装饰器支持在ArkTS卡片中使用。 从…

Chrome DevTools Protocol 进阶:Runtime 域

前言 在 Chrome DevTools Protocol&#xff08;CDP&#xff09;中&#xff0c;Runtime 域是一个非常重要的部分&#xff0c;它主要用于与 JavaScript 的执行环境进行交互。通过 Runtime 域&#xff0c;开发者可以在页面上下文中执行 JavaScript 代码、评估表达式、捕获异常等操…

Android 亮度调节

frameworks/base/packages/SystemUI/src/com/android/systemui/settings/BrightnessController.java 更新屏幕亮度值Settings.System.SCREEN_BRIGHTNESS BrightnessController.java中有个onChanged事件&#xff0c;这个是更新 setBrightness(val) private void setBrightness(i…

当你访问一个网站时,数据是怎么传输的呢

摘要&#xff1a; 本文详细阐述了电脑访问网站时数据从电脑端逐步传输到服务器端的完整过程&#xff0c;深入剖析了每一步骤中数据的形态变化以及所涉及的网络协议&#xff0c;包括应用层的 HTTP 与 DNS、传输层的 TCP、网络层的 IP 以及数据链路层的以太网等协议的协同工作机制…