重载new和delete

C++内存管理】重载 new 本文主要探讨了C++中的new操作符重载。首先解释了为什么需要重载new,如监测内存分配释放,内存对齐处理,以及多进程内存共享等。然后介绍了如何在全局范围内重载newdelete,包括如何添加自定义行为。紧接着,我们讨论了针对特定类重载newdelete的方法,这对需要特殊内存管理的类来说非常有用。最后,我们探讨了放置new的使用,这是一种在已分配的内存上创建对象的方法。通过这篇文章,您将了解到如何灵活运用C++new操作符重载,以适应各种特殊的内存管理需求。 阅读详情

重载new和delete


当我们创建一个new表达式时,会发生两件事,首先,使用operator new()来分配内存,然后调用构造函数。在delete表达式里,调用了析构函数,然后使用operator delete()释放内存。我们无法控制构造函数和析构函数的调用(这是编译器自动做的事),但可以改变内存分配函数operator new()operator delete()

1.为什么要重载

使用了new和delete的内存分配系统是为通用目的而设计的。但在特殊情况下,它不能满足需要(比如假设默认的分配策略适合分配小的内存块,而我们经常需要分配大块内存)。C/C++允许重载new和delete来实现我们自己的内存分配策略。

2.重载时发生了什么

当重载new和delete时,我们只是改变了原有的内存分配策略,记住这点很关键。编译器将用重载后的new代替默认版本取分配内存,然后为那个内存块调用构造函数。所以,当编译器看到new时,编译器分配内存并调用构造函数,但是当重载new时,可以改变的只是内存分配部分(delete也一样)。

当重载operator new()时,也可以替换它用完内存时的行为,所以必须在operator new()里决定做什么:返回0、写一个调用new-handler的循环、再试着分配或者产生bad_alloc的异常信息。

重载new和delete与重载其他运算符一样。但可以选择重载全局内存分配函数或者是特定类的内存分配函数。

3.如何重载

重载全局的new和delete

当全局版本的new和delete不能满足需要时,对其重载是很极端的方法。如果重载了全局版本,那么默认版本将完全无法访问(在这个重新定义里也不能调用它们)。

重载的new必须有一个size_t类型的参数,这个参数由编译器产生并传递给我们,它是要分配内存的对象的长度(使用new时,后面接一个对象,编译器会知道这个对象的大小)。必须返回一个指向等于(或者大于)这个长度的对象的指针,如果没有找到可用内存,则返回0。然而如果找不到可用内存,不能仅仅返回0,也许还应该做一些诸如调用new-handler或者产生一个异常信息之类的事。

operator new()返回值是一个void*,而不是指向某个具体类型的指针。所做的事情只是分配内存,而不是完成一个对象的建立——直到调用了构造函数才算完成了对象的构建,它是编译器确保的动作,不在我们可控范围内。

oeprator delete()的参数是一个指向由operator new()分配的内存的void*。参数是一个void*是因为它是调用析构函数之后得到的指针。析构函数从存储单元中移除对象。operator delete()返回值是void。

煮个栗子:

//GlobalOperatorNew.cpp
//Overloat global new/delete
#include <cstdlib>
#include <cstdio>

using namespace std;

void* operator new(size_t sz){
    printf("operator new: %d Bytes\n", sz);
    void *m = malloc(sz);
    if(!m)  puts("out of memory");
    return m;
}

void operator delete(void *m){
    puts("operator delete");
    free(m);
}

class S{
    int i[100];
public:
    S() { puts("S:S()"); }
    ~S() { puts("S:~S()");  }
};

int main(){
    puts("creating & destroying an int");
    int *p = new int(47);
    delete p;
    puts("creating & destroying an S");
    S *s = new S;
    delete s;
    puts("creating & destroying S[3]");
    S *sa = new S[3];
    delete []sa;
}

这里可以看到重载new和delete的通常形式,这里的内存分配使用了标准C函数库的mallocfree(可能默认的new和delete也使用这些函数)。注意,这里打印信息使用的是printf()和puts()而不是iostreams。因为,当创建一个iostream对象(像cin,cout和cerr),它们调用new取分配内存。而用printf()不会进入死锁状态,因为它不调用new来初始化。

对于一个类重载new和delete

为一个类重载new和delete时,尽管不必显式的使用static,但实际上仍然是创建static成员函数。它的语法也和重载其他运算符一样。当编译器看到使用new创建自己定义的类的对象时,它选择成员版本的operator new()而不是全局版本的new()。但是全局版本的new和delete任然为其他类型所使用。
下面的例子里为类Framis创建了一个简单的内存分配系统。程序开始时在静态数据区留出一块存储单元。这块内存被用来为Framis类型的对象分配存储空间。

//Framis.cpp
//Local overloaded new & delete
#include <cstddef>  //Size_t
#include <fstream>
#include <iostream>
#include <new>

using namespace std;

ofstream out("Framis.out");

class Framis{
    enum { sz = 10; };   //古老的定义类内静态常量成员的方法
    char c[sz]; //为了占据空间,不使用
    static unsigned char pool[];
    static bool alloc_map[];
public:
    enum { psize = 100; };
    Framis() { out << "Framis()\n"; }
    ~Framis() { out << "~Framis()\n"; }
    void * operator new(size_t) throw(bad_alloc);
    void operator delete(void *) ;
};
unsigned char Framis::pool[psize * sizeof(Framis)];
bool Framis::alloc_map[psize] = {false};

void* Framis::operator new(size_t) throw(bad_alloc){
    for(int i = 0; i < psize; i++){
        if(!alloc_map[i]){
            out << "using block " << i << "...";
            alloc_map[i] = true;
            return pool + (i * sizeof(Framis));
        }
    out << "out of memory" << endl;
    throw bad_alloc();
}

void Framis::operator delete(void *m){
    if(!m) return ;
    unsigned long block = (unsigned long)m - (unsigned long)pool;
    block /= sizeof(Framis);
    out << "freeing block" << block << endl;
    alloc_map[block] = false;
}

int main(){
    Framis * f[Framis::psize];
    try{
        for(int i = 0; i < Framis::psisz; i++){
            f[i] = new Framis;
        new Framis;     //out of memory
    } catch(bad_alloc){
        cerr << "Out of memory!" << endl;
    }
    delete f[10];
    f[10] = 0;
    Framis *x = new Framis;
    delete x;
    for(int i = 0; i < Framis::psize; i++)
        delete f[i];    //Delete f[10] OK
}

为一个类数组重载new和delete

如果为一个类重载了operator new和operator delete,那么无论何时创建一个这个类的对象,都会调用重载的new和delete。但如果要创建这个类的一个数组的话,编译器会使用全局的new和delete。所以我们还要重载这个类的new的数组版本。即operator new[]和operator delete[]。

//ArrayOperatorNew.cpp
#include <new>  //size_t definitoin
#include <fstream>
using namespace std;
ofstream trace("ArrayOperatorNew.out");

class Widget{
    enum { sz = 10; };
    int i[sz];
public:
    Widget() { trace << "*"; }
    ~Widget() { trace << "~"; }
    void* operator new(size_t sz){
        trace << "Widget::new: " << sz
            << " bytes" << endl;
        return ::new char[sz];
    }
    void operator delete(void *m){
        trace << "Widget::delete" << endl;
        ::delete []p;
    }
    void* operator new[](size_t sz){
        trace << "Widget::new[]: " << sz 
            << " bytes" << endl;
        return ::new char[sz];
    }
    void operator delete[](void *p){
        trace << "Widget::delete[]: " << endl;
        ::delete []p;
    }
};
int main(){
    trace << "new Widget " <<endl;
    Widget* w = new Widget;
    trace << "\ndelete Widget" << endl;
    delete w;
    trace << "\nnew Widget[25]" << endl;
    widget* wa = new Widget[25];
    trace << "\ndelete []Widget" << endl;
    delete []wa;
}

输出信息:

new Widget 
Widget::new: 40 bytes
*
delete Widget
~Widget::delete

new Widget[25]
Widget::new[]: 1004 bytes
*************************
delete []Widget
~~~~~~~~~~~~~~~~~~~~~~~~~Widget::delete[]: 

这里重载的new和delete只是一个套用了全局new和delete的壳子,当然,可以在重载的new和delete里使用任意的内存分配方案。

在语法上,除了多一对括号之外,数组版本的new和delete与单个对象版本的是一样的。不管是哪个版本,我们都要决定所要分配的内存的大小(根据编译器给出的形参决定,可以大于)。数组版本的大小指的是整个数组的大小。应该明确,重载new唯一要做的是返回一个足够大的内存块的指针。然后编译器负责初始化它。

使用数组版本的new时,需要的长度比期望的多了4个字节。这额外的4字节是系统用来存放数组信息的,特别是数组对象的数量。当用delete []widget;表达式时,方括号就告诉编译器他是一个数组,所以编译器产生寻找数组大小的代码,然后多次调用析构函数。

4.分配失败怎么办

如果使用了new的内存分配没有成功,构造函数不会调用,所以虽然没有成功的创建对象,但至少没有调用构造函数并传给它一个为0的this指针。在以前的C++版本中,如果内存分配失败,一般是返回0。它将是构造函数不被调用。如果试着在一个标准的编译器中由new返回0值,则会被告知应该产生一个bad_alloc。

5.定位new和delete

重载operator new还有两个不常见的用途:

  1. 在内存的指定位置放置一个对象。
  2. 让使用new的程序员可以选择不同的内存分配方案

这两个特性可以用相同的机制实现: 重载的operator new()可以带一个或多个参数。第一个参数总是对象的长度,它在编译器内部计算出来并传递给new,但其他参数可以由我们定义:一个放置对象的地址、一个是对内存分配函数或对象的引用,或其他设置。

在调用过程中传递额外参数给operator new的方法看起来有点古怪:在关键字new后是参数表(没有size_t参数,它由编译器处理),参数后面是正在创建的对象的类名字。例如:
X* xp = new(a) X;
a作为第二个参数传递个operator new。注意,这是在operator new已经声明的情况下才有效。

//PlacementOperatorNew.cpp
#include <cstddef>
#include <iostream>
using namespace std;

class X{
    int i;
public:
    X(int ii = 0) i(ii) {
        cout << "this = " << this << endl;
    }
    ~X(){
        cout << "X::~X(): " << this << endl;
    }
    void* operator new(size_t,void *loc){
        return loc;
    }
};
int main(){
    int l[10];
    cout << "l = " << l << endl;
    X* xp = new(l) X(47);
    xp->X::~X();    //显式调用析构函数
}

虽然本例只使用了一个附加参数,但若要实现其他目的,使用更多的参数也是可以的。
在销毁对象时会出现两难的局面。因为仅有一个版本的operator delete,所以没有办法说“对这个对象使用我的特殊的内存释放器”,所以可以调用析构函数,但不能使用动态内存释放机制释放内存,因为内存不是在堆上分配的(本例中该数组是在一个局部有效的栈上)。解决方法是非常特殊的语法:显示调用析构函数。

显示调用析构函数可能出现一些问题。由于某些人想要实时地决定对象的生存时间时,他们使用这种方法在范围结束之前销毁对象,而不是调节作用范围或者使用动态对象创建。而如果使用这种方法为在栈上的对象调用析构函数时,将会出现严重的问题,因为析构函数在对象超出作用范围时又会被调用一次。如果为在堆上创建的对象用这种方法调用析构函数,析构函数将被执行,但是内存不会被释放。用这种方法显式调用析构函数,其实只有一个原因,即支持operator new的定位语法。

还有一个定位operator delete,它仅在一个定位operator new表达式的构造函数产生一个异常信息时才被调用(因此该内存在异常处理操作中被自动清除了)。定位operator delete和定位operator new有一个对应的参数表,该定位operator new是指在构造函数产生异常信息之前被调用的那个。//TODO

arcgis计算栅格平均值 打开FC栅格数据非水体矢量数据 利用矢量数据提取对应栅格的非水体部分 2.1打开空间分析工具à提取分析à按掩膜提取 输入对应栅格数据矢量数据 2.2提取结果如下 进行计算FC平均值 3.1打开空间分析工具à多元分析à波段集统计 输入对应栅格输出路径 3.2统计结果如下 ... 阅读详情

相关推荐

不藏了!Wireshark 命令、捕获、过滤器大全分享给你

Wireshark,这款广受欢迎的开源网络协议分析工具,堪称网络管理员、网络安全专家网络爱好者的“显微镜”!它可以帮助我们捕获网络中的数据包,并提供详细的解码分析能力。本文将为您全面剖析 Wireshark 的强大功能,从基础到高级,让你快速掌握这个神器!💡Wireshark 是一款功能强大的网络协议分析器,最早由 Gerald Combs 于 1998 年开发,经过多年发展,已成为网络诊断分析的首选工具。

网络技术联盟站 674

C++学习笔记15

1:new实际上执行了什么操作,可能在什么步骤出现异常? 当我们使用关键字new在堆上动态创建一个对象时,它实际上了三件事: 1)获得一块内存空间; 2)调用构造函数3)返回正确的指针。 当然,如果我们创建的是简单类型的变量,那么第二步会被省略。 申请内存可能new失败会抛出bad_alloc异常。 2:在C++中如何限制一个类对象只在堆上分配? 这个参考这个博文 1)只在

JackZhangNJU的专栏 332

matlab实现反步法无人艇编队运动控制仿真研究-无人艇编队-反步法设计-航向控制-非线性控制-matlab

内容概要:本文探讨了反步法无人艇编队运动控制的理论基础与实现方法,尤其针对编队控制中的航向控制、协调律设计以及通信信息共享等方面进行了详细阐述。文中通过动力学建模建立无人艇的数学模型,并采用递归设计方法解决了非线性系统的稳定性问题。实验结果显示,所提出的控制器能有效地解决系统中存在的各种不确定性外部干扰,实现了高精度的编队保持高效的障碍规避。适用人群:适用于有志从事无人系统研究的高校师生技术爱好者。使用场景及目标:适用于科研工作者开展无人驾驶船舶相关课题的研发工作;可用于教学演示学生项目实践中教授非线性控制技术编程技巧;也可以为企业提供技术支持,助力新型无人艇产品研发。其他说明:文章不仅关注理论层面的内容介绍,同时也重视实用价值的应用转化,附带详细的Matlab源代码可以帮助读者更快地上手实践,进一步探索反步法及其变种方法对于解决不同类型无人平台编队问题的价值。此外,文中讨论到了当前研究面临的一些挑战未来的改进建议。

[C++] 关于new操作符的重载

关于c++ new操作符的重载 你知道c++new操作符operator new的区别么?也许你还会问,他们有区别吗? 当你写下面这样的代码时, string *pa = new string("memory managerment"); 你使用的是new操作符,这个操作符sizeof一样,是c++语言级别支持的。你不能改变它的语义,它的事情总是不变的:分配足够的内存以容纳对象,然后调...

selfsongs 5789

重载newdelete调用构造函数析构函数顺序

环境:VS2010 问题;探索重载newdelete构造函数、修改函数的调用顺序 代码: #include &lt;iostream&gt; using namespace std; class C { float x,y; public: C(){ cout&lt;&lt;"调用构造函数"&lt;&lt;endl; } ~C(){ cout&lt;&lt;...

yanghuidang 357

C++ operator new重载调用

几个名词\color{#FF0000}几个名词几个名词——指带有除了size参数以外其他参数的operator new函数不配置空间的placement new——特指void* operator new(size_t size,void* ptr),其不能被重载,函数本身不对传入的指针任何处理。operator new函数——形式为返回void*,参数最少带有一个size_t的new函数new函数与new表达式——这部分并非标准定义,只是个人为了区分内容的个人定义,针对调用过程而言。

faybull的博客 1307

解锁C++内存魔法:newdelete重载实操指南

C++ 中,有两个运算符在动态内存分配这块起着关键作用,它们就是newdeletenew负责在堆上开辟一块新的内存空间,同时还会调用对象的构造函数完成初始化工作;delete则是用来释放new分配的内存,释放前会先调用对象的析构函数清理资源 。比如说,当我们要创建一个动态数组时,就可以用int* arr = new int[10];这样的语句,这里new分配了能存放 10 个整数的内存空间,之后如果不再需要这个数组了,就得用delete[] arr;来释放内存,不然就会造成内存泄漏。

大雨淅淅的博客 1380

C++newdelete重载

首先要知道newdelete是一种运算符,而mallocfree是一种系统调用函数,因此newdelete是可以重载的。 先来举个例子看一下newdelete malloc与free的区别:class A{ public: A(){ cout << "constuctor" << endl; } ~A(){ cout << "di

maoliran的博客 895

newdelete重载

1. 简介 new/delete关键字,其本质是预定义的操作符,因此支持重载 默认newdelete的行为: new: ①获取内存空间(默认为堆空间);②在获取的空间中调用构造函数创建对象 delete:①调用析构函数销毁对象;②归还对象所占用的空间(默认为堆空间) 2.C++new/delete操作符重载的两种方式 全局重载:会影响所有的类(不推荐) 局部重载:针...

Young's Blog 676

vc中newdelete操作符的重载

2019独角兽企业重金招聘Python工程师标准>>> ...

weixin_34297704的博客 174

newdelete重载

newdelete重载

qq_48083892的博客 265

重载全局成员new/delete

1. 重写全局new/delete 重载::operator new、::operator delete、::operator new[]、operator delete[] 注意:如果重载的是全局的,所以创建对象时编译器调用operator new、operator delete时都会去调用自己重载的版本;如果重载的类成员operator new,那么只在创建释放该类对象时编译器调用自己重载的operator new、operator delete版本。 如果Fraction类没有重写本来的new/de

qq_45164331的博客 620

newdelete重载(1)

 newdelete是语言关键字,它分为两个部分new:operator new(申请空间)------构造函数初始化delete:析构函数析构---------------operator delete(释放空间)而在operator new operator delete中又分为global newplacement new以及global deleteplacemen

jacob_007的专栏 4886

关于重载new/delete

#include #include using namespace std;class Desk{public: Desk() { cout<<"Desk::constructor"<<endl; } ~Desk() { cout<<"Desk::destructor"<<endl; }};void* operator new(siz

lizhongkan的专栏 683

重载new delete

new 表达式的工作机理如下: 第一步:调用一个名为operator new(或者operator new[])的标准库函数 该函数分配一块足够大的,原始的,未命名的内存空间以便存储特定类型的对象(或者对象数组) 第二步:编译器运行相应的构造函数以构造这些对象,并为其传入初始值。 第三步:对象被分配了空间并构造完成,返回一个指向该对象的指针 delete 表达式的工作机制如下: 第一步:对对象调用

pzh的博客 1254

c/c++ 重载newdelete运算符 placement new

重载newdelete运算符 newdeletec++中也被归为运算符,所以可以重载它们。 new的行为: 先开辟内存空间 再调用类的构造函数 开辟内存空间的部分,可以被重载delete的行为: 先调用类的析构函数 再释放内存空间 释放内存空间的部分,可以被重载。 为什么要要重载它们? 有时需要实现内存池的时候需要重载它们。频繁的new...

forfuture3513的博客 595

重载operator new operator delete

 1.创建类Foo     在类Foo中重载operator new operator delete。 class Foo { private: int _id; public: Foo() :_id(0) { cout &lt;&lt; "Default Construct " &lt;&lt; this &lt;&lt; " id = " &lt;&lt; _id &l

万里归来少年心 707

c++重载new操作符

newC++里非常重要的一个关键词,用于申请内存、初始化对象。俗话说“有借有还再借不难”,通过new向操作系统“借”到的内存用完后必然要“还”回去,所以对应地还有一个delete操作符与new共同管理内存,delete的作用是析构对象、释放内存。 new有什么作用? 申请内存 初始化对象 说到内存管理,有些同学会想到C标准库函数malloc()free()。C++是C语言的延续,那么C++一定可以丝滑地使用这两个标准库函数管理内存,那为什么还要提供关键词newdelete呢? 我们申请到内存后一般

米碎师兄 3921
上一篇: GCC内联汇编
下一篇: 句柄类
InvokerZhang
博客等级 码龄11年 9粉丝 24原创
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值