一篇文章了解c++中的new和delete

C语言提供了malloc和free两个系统函数,完成对堆内存的申请和释放,而C++则提供了两个关键字new和delete,下面这篇文章主要给大家介绍了如何通过一篇文章了解c++中new和delete的相关资料,需要的朋友可以参考下

执行完该代码后内存分配如下

new的内存分布

下图是vc6中new的内存布局

placement new

placement new 允许我们将对象构建于一个已经分配的内存当中

没有所谓的placement delete,因为placement根本就没有分配内存,它只是使用了一个已经分配好的内存,所以不需要配套的释放操作,具体用法如下


#include <new>

// 分配内存
char *buf = new char[sizeof(Complex) * 3];

// 在分配好的内存上构造Complex
Complex *pc = new(buf)Complex(1, 2);

// 注意这里要释放的指针
// 感觉如果直接释放pc应该也没错
// 手上没环境不能测试,以后有时间测一下
delete[] buf;

new失败处理

在纯C中使用malloc来分配内存,需要判断一下返回的指针,如果返回一个空指针,则代表内存分配失败。

到了c艹中,使用new来分配内存,则无法通过判断空指针的方法判断是否失败。因为在c艹中,如果new失败会抛出异常,代码是走不到判断空指针的语句的。new失败正确处理方法有以下几种

捕捉异常


try 
{
    int* p = new int[SIZE];
    // 其它代码
} catch ( const bad_alloc& e ) 
{
    return -1;
}

据说古老的c++编译器new失败不会抛异常,而是和malloc一样返回空指针,因为那时候c++还没有异常机制,坊间流传,也懒得考证,了解以下即可。顺便吐槽一下,说c艹的异常是屎,这是对屎的侮辱,屎还能当肥料种地呢,c艹的异常除了捣乱没任何鸟用。

禁用new的异常


 int* p = new (std::nothrow) int; // 这样如果 new 失败了,就不会抛出异常,而是返回空指针

new-handler

文章开始介绍new源码的时候提到过,new实现的时候会调用new-handler的回调函数,在new之前设置好回调函数即可。由于此方法太过麻烦,懒得研究,具体用法读者自行查找相关资料。

重载

重载全局的::operator new


void *myAlloc(size_t size)
{ return malloc(size); }

void myFree(void *p)
{ free(p); }

// 下面代码实现部分不重要,关键看接口的重载
// 它们不可以被声明在一个namespace内
inline void *operator new(size_t size)
{ cout << "global new()\n"; return myAlloc(size); }

inline void *operator new[](size_t size)
{ cout << "global new[]()\n"; return myAlloc(size); }

inline void operator delete(void *p)
{ cout << "global delete()\n"; return myFree(p); }

inline void operator delete[](void *p)
{ cout << "global delete[]()\n"; return myFree(p); }

重载局部的Foo::operator new


class Foo
{
public:
    void *operator new(size_t);
    void operator delete(void*);
};

需要注意的是,重载局部的new和delete必须是static的,因为new调用时是内存对象创建过程当中,此时还没有一个完整的内存对象,无法通过对象来调用一般的函数。由于必须是static的,不管写不写static,编译器都会当成是static处理。

数组版本也是一样的,只是都加了一个[],这里就不再写一次了

重载placement new

placement new的括号中不一定非要放指针,我们可以自己来定义放任意的东西。放指针的版本是标准库中先写好给我们用的,我们也可以通过重载placement new来自定义所放的数据,比如Foo *pf = new(300, 'c')Foo;。可以重载为多种参数形式,但多个重载的参数列形式不能重复,必须满足普通函数重载的条件。其中第一个参数必须是size_t,用来传递类的大小,该参数类似于成员函数的this指针,在调用时自动传递,不需要显示传递。比如在Foo *pf = new(300, 'c')Foo;中,其声明形式为void *operator new(size_t, int, char);。如果内存不是外部申请好的,需要在placement new函数内部去申请内存。

重载new的时候应该对应重载一个相同形式的delete。但重载placement delete时需要注意,只有在placement new中产生异常,才会调用其对应的placement delete函数。c++这么设计的原因是,在调用placement new函数后,如果内存是由在placement new内申请的,在调用构造函数时如果发生了异常,可以在对应的在placement delete函数中将在placement new中申请的内存释放掉。

如果没有对应形式的delete,编译器也不会报错,编译器会认为你放弃处理该形式的new中产生的异常(个别编译器会给个警告)


class Foo
{
public:

    // 重载一个一般形式的operator new
    void *operator new(size_t);

    // 标准库中placement new的重载形式
    void *operator new(size_t, void *);

    // Foo *pf = new(300, 'c')Foo;调用形式的重载方式
    void *operator new(size_t, int, char);

    // 随便写的一种重载形式
    void *operator new(size_t, size_t, char *, int);

    // 以下是对应的delete
    void *operator delete(void *, size_t);
    void *operator delete(void *, void *);
    void *operator delete(void *, int, char);
    void *operator delete(void *, size_t, char *, int);
};

std::string中就是一个很好的placement new重载,有兴趣的朋友可以去看string的源码

c++ new与malloc的10点差别表格:

特征 new/delete malloc/free
分配内存的位置 自由存储区
内存分配失败返回值 完整类型指针 void*
内存分配失败返回值 默认抛出异常 返回NULL
分配内存的大小 由编译器根据类型计算得出 必须显式指定字节数
处理数组 有处理数组的new版本new[] 需要用户计算数组的大小后进行内存分配
已分配内存的扩充 无法直观地处理 使用realloc简单完成
是否相互调用 可以,看具体的operator new/delete实现 不可调用new
分配内存时内存不足 客户能够指定处理函数或重新制定分配器 无法通过用户代码进行处理
函数重载 允许 不允许
构造函数与析构函数 调用 不调用

malloc给你的就好像一块原始的土地,你要种什么需要自己在土地上来播种

而new帮你划好了田地的分块(数组),帮你播了种(构造函数),还提供其他的设施给你使用:

当然,malloc并不是说比不上new,它们各自有适用的地方。在C++这种偏重OOP的语言,使用new/delete自然是更合适的。

总结

到此这篇关于c++中new和delete的文章就介绍到这了,更多相关c++中new和delete内容请搜索编程学习网以前的文章希望大家以后多多支持编程学习网!

本文标题为:一篇文章了解c++中的new和delete