【C++】动态内存管理和智能指针

本文探讨了C++中动态内存管理的区别,如new与malloc,以及为何需要智能指针来避免内存泄漏。详细介绍了智能指针的不同类型,如auto_ptr、scoped_ptr、shared_ptr和weak_ptr的工作原理及使用注意事项,强调了智能指针在解决内存管理问题上的重要性。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

C++动态内存管理的方式

malloc和new,free和delete

在C语言中,我们通常用malloc和free来动态的管理内存,其中malloc用来在堆上开辟空间,而free用来释放malloc或其他在堆上动态开辟内存的函数所开辟的空间。在C++中,我们用new/delete;new[]/delete[]来动态的管理内存,相比于C语言中的malloc和free,他们之间有什么差别呢?

首先,它们的调用方式不相同,其中malloc和free采用下面的方式来调用

 int* p=(int*)malloc(sizeof(int)*4);
 //使用malloc函数开辟4个int类型的空间

free(p);
p=NULL;
//释放上面malloc所开辟的空间

在C++中,new和new[]/delete[]和delete主要采用下面的调用方式:

    int* p=new int(4);       //开辟一个int类型的对象,初值为4
    delete p;               //释放上面所开辟的空间

    int* p1=new int[4];     //开辟4个int类型的空间,无初值
    delete[] p1;            //释放上面所开辟的空间

另外,它们之间更重要的差别是,new和delete,new[]和delete[]才开辟空间后和释放空间前还分别调用了所开辟类型的构造函数以及析构函数。如下图所示:
new
delete

另外,C中的malloc开辟空间时如果出现了内存耗尽的情况的话会直接返回NULL,而C++中的new会抛出异常,不过这里我们也可以声明其不抛出异常使其在内存耗尽的情况下返回NULL.

   int* p=new int[100000000000];       //出现内存耗尽,抛出异常的方式

   int* p1=new(nothrow) int[10000000]; //不抛出异常,出现内存耗尽返回NULL

为什么需要智能指针

在平时写代码时,我们用new开辟内存空间时,即使我们记住了需要配对使用,但难免有时候会出现内存泄漏的情况;例如:

   {
       int* p=new int(5);
       ...
       ...
        thorw ...;
`      ...
       delete p;
   }

在上面的这段代码中,即使我们在最后对new分配的空间进行了delete,如果中间因为某些原因我们抛出了一个异常,并且我们在异常处理函数中没有对这块内存进行处理,那么这块内存便会一直停留在操作系统中,从而导致了内存泄漏。

对于这种情况,我们因此引入了智能指针,智能指针采用RAII(资源分配即初始化机制)的内存管理方式,即智能指针不是常规指针,只是他的行为类似指针,其实是一种管理内存的对象,在其构造时获取资源,在其对象生命控制期对其访问使用有效,最后在其析构期释放内存。对此不管是否抛出异常,只要离开了当前的作用域,作用域中的智能指针便会自动调用delete或delete[]进行释放资源。
在boost库中主要有下面几种智能指针:
这里写图片描述

auto_ptr

auto_ptr采用可以采用copy语义来转移指针资源的所有权的同时将原指针置为NULL,这跟通常理解的copy行为是不一致的,而这样的行为要有些场合下不是我们希望看到的,例如sort的快排实现中有将元素复制到某个局部临时对象中,但对于auto_ptr,却将原元素置为null,这就导致最后的排序结果中可能有大量的null从而导致异想不到的后果。

scoped_ptr

scoped_ptr和auto_ptr都表示唯一的所有权持有者,区别在于scoped_ptr不允许拷贝构造和赋值的发生,它这这些函数定义为私有成员函数,从而使外部对象无法进行访问。
下面我们来实现一个简单的scoped_ptr

template<class T>
class Scoped_ptr
{
public:
    explicit Scoped_ptr(T* ptr=NULL)
        :_ptr(ptr)
    {}

    T& operator*()
    {
        return *_ptr;
    }

    T* operator->()
    {
        //return &(operatpr*());   //写法1
        return _ptr;
    }

    ~Scoped_ptr()
    {
        delete _ptr;
    }
private:
    Scoped_ptr(const Scoped_ptr& sp)
    {}

    operator=(const Scoped_ptr& sp)
    {}
protected:
    T* _ptr;
};

其中,编译器对->运算符的重载进行了优化
例如:

    struct A
    {
        int _a;
        int _b;
        int _c;
    };
    Scoped_ptr<A> pa(new A());
    pa->_a;
    //(pa->_a):pa.operator->->_a;       两步变一步 

shared_ptr

shared_ptr采用了引用计数的方式来管理内存,如下图:
这里写图片描述

使用库中的shared_ptr指针:
* shared_ptr不允许隐式的强制类型转换(构造函数和拷贝构造函数前面声明了explicit)
例如:不能这么使用shared_ptr

    shared_ptr<int> pa=new int(1);

    //正确使用方式如下
    shared_ptr<int> pa(new int(1));
  • 返回值必须要显示的绑定到shared_ptr的指针上
    例如:
    shared_ptr<int> ReturnPtr()
    {
        //正确方式
        shared_ptr<int> tmp(new int(1));
        return tmp;

        //错误方式
        return new int(1);
    }
  • p.reset() //若p的shared_ptr的引用计数为1,reset会释放p所指向的对象
  • make_shared //创建一个shared_ptr的对象
  • 使用shared_ptr的误区:使用内置指针来访问shared_ptr和使用shared_ptr来管理内置指针
  • 智能指针类型定义了一个名为get的函数,它返回一个内置指针,指向智能指针管理的对象(注意在使用get时不能delete指针,否则后面可能会发生对空指针的解引用的行为)

weak_ptr

这里写图片描述
此时weak_ptr就出现了,它采用弱引用的方式,弥补了shared_ptr循环引用的问题它的构造和析构不会引起引用记数的增加或减少。没有重载*和->但可以使用lock获得一个可用的shared_ptr对象。

weak_ptr:

namespace boost 
{
 template<typename T> class weak_ptr
 {
     public:
       template <typename Y>
       weak_ptr(const shared_ptr<Y>& r);

       weak_ptr(const weak_ptr& r);

       ~weak_ptr();

       T* get() const; 
       bool expired() const; 
       shared_ptr<T> lock() const;
   }; 
 }

总结::智能指针能解决C++中的内存泄漏问题,我们应在程序中多去使用智能指针,另外还要注意shared_ptr引起的循环引用的问题。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值