首页 > 编程语言 >C++资源管理浅谈

C++资源管理浅谈

时间:2024-09-15 16:19:46浏览次数:3  
标签:std 浅谈 C++ ptr 内存 new PInv 资源管理 delete

引言:      

        在计算机编程语言的学习与实践中,自然避免不了与计算机的资源管理打交道。所谓的资源就是,一旦用了它,将来就必须还给系统,如果用户不这么做,那糟糕的事情便会发生。在开始谈及C++的资源管理之前,先来聊聊何为计算机的资源,以及为何要管理计算机的资源。

        在编程中,资源指的是计算机系统中具有有限性共享性的各种实体或能力,它们通常是系统运行时需要使用的硬件或软件要素。

        为何要进行资源管理?因为资源是有限的,不可以无止尽进行索取。正犹如存在一间房间,其容积便是用户可以使用的资源。如果不断添加家具而不清理旧家具,空间会变得拥挤,最终直至于无地栖身。程序上也是一样的,用户不断向系统索取资源,而不将其进行释放归还给系统,久而久之,系统表现愈来愈差,最后便可能会崩溃,卡住。因此,为了使程序能够良好运作,对资源进行合理的管理是必不可少的。


自动内存管理:

垃圾回收机制(Garbage Collection)

        垃圾回收是自动内存管理最广泛使用的机制,主要工作在堆内存上。许多现代编程语言,如 Java、C#、Python 和 JavaScript,都使用垃圾回收机制。垃圾回收器的工作原理是自动跟踪不再使用的内存并回收这些内存。这种机制的优点很明显,能避免用户犯下一些低级的错误。当然,也带来了额外的性能开销。此处不深入讨论。

栈内存的自动管理

        在编写C/C++程序中,我们可以使用mallocnew等语句来在堆区(heap)上开辟空间,也有相应的语句对其开辟的空间进行释放。但是在栈区上却并非如此,我们不需要手动在栈区上分配空间,更不需要手动释放,而这一切都得利于栈空间中的自动内存管理模式:由系统全权管理,局部变量通常在栈上分配,函数执行完毕时,栈帧自动销毁,释放内存。由于栈是 "LIFO"(后进先出)结构,因此内存的分配和回收是非常高效的。

部分语言也有自己独特的自动内存管理机制,此处就不一一列举。


手动内存管理:

常规使用new,delete手动进行管理

        在 C++ 中,newdelete 是用于动态内存管理的关键字,分别负责在堆上分配和释放内存。用户在使用这些关键字管理内存时,应当留意以下事项:

  • newdelete的使用必须成对!

void dynamic_memory_allocate()
{
    /*在堆区开辟大小为4字节的空间用于存储一个int型数据
    int型指针iptr指向该空间*/ 
    int* iptr = new  int;  
    *iptr = 10;
    /*忘记了delete行为,导致内存泄漏*/
}

        

void dynamic_memory_allocate(int* PInv)
{
    int *iptr = new int[5];
    *PInv = 20;
    *(iptr+2) = *PInv;
    delete[] iptr;  //delete遗漏,导致内存泄漏
}

int main(int argc,const char* argv[])
{
    dynamic_memory_allocate(new int);

    return 0;
}
  • 释放对象后防止指针悬空!

void dynamic_memory_allocate()
{
    int *PInv = new int;
    delete PInv;
    *PInv = 15;	//悬空指针
    /*PInv所指向的内存被释放后仍然操纵指针。这可能会引发未定义行为*/
}

解决办法之一便是释放对象后,手动将指针置空。

void dynamic_memory_allocate()
{
    int *PInv = new int;
    delete PInv;
    PInv = nullptr;
}
  • 成对的newdelete形式应相同!

        在用new开辟空间时,其所针对的是单个对象还是一个对象数组?这个问题十分重要,因为c++中释放存在两种形式:delete | delete[]

        前者用于释放动态分配的单个对象,而后者则用于释放动态分配的对象数组,二者不可混用!因为newdelete会自动调用构造与析构函数,这块内存中存在多少个对象?这个问题的答案直接影响了该有多少个析构函数该被调用起来。倘若混用,则很可能会导致未定义行为发生。

void dynamic_memory_allocate()
{
    typedef std::string MyTelephoneNum[5];
    std::string *PInv = new MyTelephoneNum;
    //delete PInv;  错误,会导致未定义行为
    delete[] PInv;  //正确,正常运行
    PInv = nullptr;
}

智能指针对象自动管理

        为了减少手动管理资源时的复杂性和常见错误,C++中引入了智能指针的概念。所谓智能指针,是指封装了原始指针的类,通过 RAII(Resource Acquisition Is Initialization)机制自动管理内存。

  • std::unique_ptr

        其前身为std::auto_ptr,在C++11之后auto_ptr便被废除,取而代之的则是unique_ptrstd::unique_ptr 是一个独占所有权的智能指针,意味着只有一个 unique_ptr 可以拥有某块内存。当 unique_ptr 离开其作用域时,它会自动释放所拥有的内存。相比 auto_ptrunique_ptr 提供了更安全、清晰的语义。unique_ptr 不允许拷贝,但可以通过移动语义来转移所有权,这使得资源管理更加清晰和安全。

#include <memory>	//使用时应当注意包含头文件<memory>
void dynamic_memory_allocate()
{
    std::unique_ptr<std::string> PInv(new std::string);
    *PInv = "Hello,Cpp";
    /*将开辟的空间与unique_ptr对象挂钩,不需要手动delete,当离开该作用域指针变量被销毁时
    自动调用delete进行释放*/
}
  • std::shared_ptr

        顾名思义,既然unique是独占所有权指针,不可以有多个unique指针维护同一个对象。那shared_ptr则允许用户用多个指针维护同一个对象。即std::shared_ptr 是一个共享所有权的智能指针,多个 shared_ptr 可以同时拥有一块内存,该型指针内部维护有一个计数器,记录维护同一块空间的指针对象数目,当该数目清0时,方才调用delete进行释放。也就是说,只有当维护一块空间的最后一个指针变量离开其作用域,才会执行释放动作。

std::shared_ptr<int> dynamic_memory_allocate()
{
    std::shared_ptr<int> PInv(new int);
    std::shared_ptr<int> PInv_2 = PInv; //此种赋值在unique_ptr是不被允许的
    /*PInv_2与PInv共同维护一块内存*/
    return PInv_2;
    /*返回后PInv与PInv_2被销毁,但是计数器并未归0,因此对象并没有被释放*/
}

int main(int argc,const char* argv[])
{
    /*PInv_3同样维护这块空间,只有当该作用域结束,PInv_3被销毁时方才会真正释放这片空间*/
    std::shared_ptr<int> PInv_3 = dynamic_memory_allocate();

    return 0;
}
  • std::weak_ptr

  weak_ptr是伴随着shared_ptr而产生的一种智能型指针。用于解决 std::shared_ptr 相互引用时可能导致的循环引用问题。考虑代码如下:

class B;
class A {
public:
    std::shared_ptr<B> PInvA;
    A() : PInvA(new B) {}
};

class B {
public:
    std::shared_ptr<A> PInvB;
    B() : PInvB(new A) {}
};

void createCycle() {
    std::shared_ptr<A> a(new A());
    std::shared_ptr<B> b(new B());
    a->PInvA = b;
    b->PInvB = a;
    /*a 和 b 在 createCycle 函数结束时会被销毁,但由于它们相互引用
,引用计数不会降为 0,导致内存不会被释放。
    从而导致内存泄漏*/
}

其解决办法便是使用weak进行观察:

class B;
class A {
public:
    std::shared_ptr<B> PInvA;
    A() : PInvA(new B) {}
};

class B {
public:
    std::weak_ptr<A> PInvB; // 使用 weak_ptr 代替 shared_ptr
    B() : PInvB(std::shared_ptr<A>(new A)) {}
};

这样,当 shared_ptr 被销毁时,引用计数会正确降为 0,内存会被释放。

在此,智能指针我们只做如此说明。


RAII(Resource Acquisition Is Initializing)资源获取即初始化机制

        RAII,与其说是机制,更是一种思想。也常被称为资源取得时机便为初始化时机。它用于确保资源在对象的生命周期内得到安全、自动化的管理。RAII的核心思想是将资源(如内存、文件句柄、锁等)的获取和释放与对象的生命周期绑定,从而避免手动管理资源可能引发的问题,如资源泄漏或不正确的释放。其核心可用一句话说明:在构造函数中获取资源,在析构函数中释放资源

        在c++编程中,RAII思想的运用尤为广泛,在动态内存、文件、锁或网络连接等方面均有应用。其基本原理便是在构造函数中关联资源的分配,在其析构函数中关联对象的释放。利用c++中对象的特殊机制,达到相对自动化管理内存的目的。

class ManageMyString
{
private:
    char *PInv;

public:
    ManageMyString(const char *str)
    {
        if (str)
        {
            PInv = new (char[strlen(str) + 1]);
            strcpy(this->PInv,str);
        }
        else
        {
            PInv = new(char[1]);
            *PInv = '\0';
        }
    }
    //禁用拷贝与赋值
    ManageMyString(const ManageMyString&) = delete;
    ManageMyString& operator=(const ManageMyString&) = delete;

    ~ManageMyString()
    {
        delete[] this->PInv;
    }
};

        以上代码便是RAII思想的一例具体应用案例:构造函数中根据传入的字符串分配对应的内存,并且将字符串复制到所分配的内存中。因此,一个ManageMyString对象便维护了一个指向字符串的指针变量。当该对象被销毁时,析构函数中调用delete释放掉所维护的内存。

        该行为与智能指针,以及在栈区上的内存管理机制相同。RAII将对象与资源管理相绑定,合理运用能够极大减轻资源管理的压力以及难度。

标签:std,浅谈,C++,ptr,内存,new,PInv,资源管理,delete
From: https://blog.csdn.net/R6bandito/article/details/142285334

相关文章

  • 【C++】string类中常用函数的模拟实现
    【C++】string类中常用函数的模拟实现1.string.h2.Text.cpp1.string.h#include<assert.h>namespacewch{ classstring { public: typedefchar*iterator; typedefconstchar*const_iterator; iteratorbegin() { return_str; } itera......
  • C++string容器
    string容器基本概念本质:string是C++风格的字符串,而string本质上是一个类string和char*区别:char*是一个指针string是一个类,类内部封装了char*,管理这个字符串,是一个char*型的容器特点:string类内部封装了很多成员方法例如:查找find,拷贝copy,删除delete替换replac......
  • C++: 二叉树进阶面试题
    做每件事之前都心存诚意,就会事半功倍.目录前言1.根据二叉树创建字符串2.二叉树的层序遍历Ⅰ3.二叉树的层序遍历Ⅱ4.二叉树的最近公共祖先5.二叉搜索树与双向链表6.根据一棵树的前序遍历与中序遍历构造二叉树7.根据一棵树的中序遍历与后序遍历构造二叉树8.二......
  • windows C++ 并行编程-并行容器和对象
    并行模式库(PPL)包括几个容器和对象,这些容器和对象提供对其元素的线程安全访问。并发容器提供对最重要操作的并发安全访问。在这里,并发安全意味着指针或迭代器始终有效。它不保证元素初始化或特定的遍历顺序。这些容器的功能与C++标准库提供的功能类似。例如,concurren......
  • windows C++-并行编程-PPL任务并行(一)
    在并发运行时中,任务是执行特定作业并通常与其他任务并行运行的工作单元。任务可以分解为组织成任务组的其他更细化的任务。编写异步代码,并希望在异步操作完成之后进行某种操作时,可使用任务。例如,可以使用一个任务以异步方式从文件读取,然后使用另一个任务(延续任务,本文档稍后......
  • c++修炼之路之AVL树与红黑树
    目录一:AVL树1.AVL树的概念2.AVL树插入数据后平衡因子及更新的情况3.AVL树节点的定义 4.AVL树的插入及旋转 二:红黑树 1.红黑树的概念及性质2.红黑树节点的定义3.红黑树的插入操作情况 4.红黑树与AVL树的比较 接下来的日子会顺顺利利,万事胜意,生活明朗---------......
  • 南沙C++信奥老师解一本通题: 1161:转进制
    ​ 题目描述】用递归算法将一个十进制数X转换成任意进制数M(M≤16)。【输入】一行两个数,第一个十进制数X,第二个为进制M。【输出】输出结果。【输入样例】3116{将十进制31转化为十六进制数}【输出样例】1F#include<iostream>usingnamespacestd;intx,m;void......
  • C++ 定义静态成员 static 关键字不能在定义出重复出现
    定义静态成员和其他的成员函数一样,我们既可以在类的内部也可以在类的外部定义静态成员函数。当在类的外部定义静态成员时,不能重复static关键字,该关键字只出现在类内部的声明语句:voidAccount::rate(doublenewRate){interestRate=newRate;}Note:和类的所有成员一样,当我......
  • C++ 3/5 法则相关
    拷贝构造函数拷贝构造函数的第一个参数必须是一个引用类型。虽然我们可以定义一个接受非const引用的拷贝构造函数,但此参数几乎总是一个const的引用。拷贝构造函数在几种情况下都会被隐式地使用。因此,拷贝构造函数通常不应该是explicit的(参见7.5.4节,第265页)。一般情况,......
  • C++ Primer Plus 第六版中文版(上)
    参考资料:《C++PrimerPlus第六版中文版》笔记作者:Mr.Crocodile欢迎转载文章目录开始学习C++头文件命名规定名称空间`cout`、`cin`函数处理数据简单变量变量命名规则整型运算符`sizeof`和头文件climitsclimits中的符号常量变量初始化整型字面量整型字面量后缀char......