动态对象创建(二)重载new和delete

前言

上文我简单介绍了一下动态对象创建的方法,这一篇文章的内容主要是对重载new和delete做一些讲解,也希望能够得到博友们的指点,在这里谢过大家。

通常我们为了一些目的而使用new和delete的内存分配系统,但是在特殊情况下,它并不能够满足需要。最常见的改变分配系统的原因是出于效率考虑:也许要创建和销毁一个特定的类的非常多的对象以至于这个运算变成了速度的瓶颈。C++允许重载new和delete来实现我们自己的存储分配方案,所以可以用它来处理问题。

另一个问题就是堆碎片:分配不同大小的内存可能会在堆上产生很多碎片,以至于很快用完内存。虽然内存可能还有,但是由于都是碎片,也就找不到足够大的内存块满足需要。通过为特定类创建自己的内存分配器,可以确保这种情况不会发生。

当我们在重载operator new()和operator delete()时,我们只是改变了原有的内存分配方法。编译器将用重载的new代替默认版本去分配内存,然后为那个内存调用构造函数。所以,虽然当编译器看到new时,编译器分配内存并调用构造函数,但是当重载new时,可以改变的只是内存分配部分。

接下来,我分为三个部分详细讲解重载new和delete:重载全局new和delete、对于一个类重载new和delete以及为数组重载new和delete。

重载全局new和delete

当我们重载全局的new和delete的时候,可想而知,就会使默认版本完全不能被访问--甚至在这个重新定义里也不能调用它们。

重载的new必须有一个size_t参数。这个参数由编译器产生并传递给我们,它是要分配内存的对象的长度。必须返回一个指向等于这个长度(或者大于这个长度)的对象的指针。如果没有找到存储单元(在这种情况下,构造函数不被调用),则返回一个0。然后如果找不到存储单元,不能仅仅返回0,我们还应该调用new-handler或产生一个异常信息之类的事,告诉这里出现了问题。

我们首先需要明白的一点就是:operator new()的返回值是一个void*,而不是指向任何特定类型的指针。所做的是分配内存,而不是完成一个对象建立--直到构造函数调用了才完成对象的创建,它是编译器确保做的动作,不在我们的控制范围之内了,所以我们就没有必要考虑。

 1 #include<iostream>
 2 #include<cstdio>
 3 #include<cstring>
 4 #include<cstdlib>
 5 #include<cmath>
 6 #include<algorithm>
 7 #define inf 0x7fffffff
 8 using namespace std;
 9
10 void* operator new(size_t sz)
11 {
12     printf("operator new: %d Bytes\n",sz);
13     void* m = malloc(sz);
14     if (!m) puts("out of memory");
15     return m;
16 }
17
18 void operator delete(void* m)
19 {
20     puts("operator delete");
21     free(m);
22 }
23
24 class S
25 {
26 public:
27     S() {puts("S::S()"); }
28     ~S() {puts("S::~S()"); }
29 private:
30     int an[1000];
31 };
32
33 int main()
34 {
35     puts("creating & destroying an int");
36     int* q = new int(23);
37     delete q;
38     puts("creating & destroying an int[]");
39     int* p = new int[10]();
40     delete []p;
41     puts("creating & destroying an s");
42     S* s = new S;
43     delete s;
44     puts("creating & destroying S[3]");
45     S* sa = new S[3];
46     delete []sa;
47     return 0;
48 }

对于一个类重载new和delete

为一个类重载new和delete的时候,尽管不必显式的使用static,但是实际上仍是在创建static成员函数。它的语法也和重载任何其它运算符一样。当编译器看到使用new创建自己定义的类的对象时,它选择成员版本的operator new()而不是全局版本的new()。但是全局版本的new和delete仍为所有其他类型对象使用(除非它们也有自己的new和delete)。这个和全局变量、局部变量的意思是一样的,应该很好懂吧。

 1 #include<iostream>
 2 #include<cstddef>
 3 #include<fstream>
 4 #include<new>
 5 using namespace std;
 6 ofstream out("Framis.out");
 7
 8 class Framis
 9 {
10 public:
11     enum{psize = 100 };
12     Framis() {out<< "Framis()" <<endl; }
13     ~Framis() {out<< "~Framis() ... " <<endl; }
14     void* operator new(size_t) throw (bad_alloc);
15     void operator delete(void*);
16 private:
17     enum{sz = 10 };
18     char c[sz];
19     static unsigned char pool[];
20     static bool alloc_map[];
21 };
22 unsigned char Framis::pool[psize*sizeof(Framis)];
23 bool Framis::alloc_map[psize]={false};
24
25 void* Framis::operator new(size_t sz) throw(bad_alloc)
26 {
27     for (int i=0; i<psize; ++i) {
28         if (!alloc_map[i]) {
29             out<< "using block " << i << " ... ";
30             alloc_map[i]=true;
31             return pool+(i*sizeof(Framis));
32         }
33     }
34     out<< "out of memory" <<endl;
35     throw bad_alloc();
36 }
37
38 void Framis::operator delete(void* m)
39 {
40     if (!m) return;
41     unsigned long block = (unsigned long)m-(unsigned long)pool;
42     block /= sizeof(Framis);
43     out<< "freeing block " << block <<endl;
44     alloc_map[block]=false;
45 }
46
47 int main()
48 {
49     Framis* f[Framis::psize];
50     try {
51         for (int i=0; i<Framis::psize; i++) {
52             f[i]=new Framis;
53         }
54         new Framis;
55     } catch(bad_alloc) {
56         cerr<< "Out of memory!" <<endl;
57     }
58     delete f[10];
59     f[10]=0;
60     Framis* X=new Framis;
61     delete X;
62     for (int j=0; j<Framis::psize; j++) {
63         delete f[j];
64     }
65     return 0;
66 }

为数组重载new和delete

上一段文字中我们讲到如果为一个类重载operator new()和operator delete(),那么无论何时创建这个类的一个对象都将调用这些运算符。但是如果要创建这个类的一个对象数组的时候,全局operator new()就会被立即调用,用来为这个数组分配足够的内存。对此,我们可以通过为这个类重载运算符的数组版本,即operator new[]和operator delete[],来控制对象数组的内存分配。

 1 #include<iostream>
 2 #include<fstream>
 3 #include<new>
 4 using namespace std;
 5 ofstream trace("ArrayOperatorNew.out");
 6
 7 class Widget
 8 {
 9 public:
10     Widget() {trace<< "*" <<endl; }
11     ~Widget() {trace<< "~" <<endl; }
12     void* operator new(size_t sz) {
13         trace<< "Widget::new: " << sz << " byte" <<endl;
14         return ::new char[sz];
15     }
16     void operator delete(void* p) {
17         trace<< "Widget::delete" <<endl;
18         ::delete []p;
19     }
20     void* operator new[](size_t sz) {
21         trace<< "Widget::new[]: " << sz << " bytes" <<endl;
22         return ::new char[sz];
23     }
24     void operator delete[](void* p) {
25         trace<< "Widget::delete[]" <<endl;
26         ::delete []p;
27     }
28 private:
29     enum{sz=10 };
30     int an[sz];
31 };
32
33 int main()
34 {
35     trace<< "new Widget" <<endl;
36     Widget* w=new Widget;
37     trace<<endl<< "delete Widget" <<endl;
38     delete w;
39     trace<<endl<< "new Widget[25]" <<endl;
40     Widget* wa=new Widget[25];
41     trace<<endl<< "delete []Widget" <<endl;
42     delete []wa;
43     return 0;
44 }

总结

1:对于C++中的动态对象创建又有了新的认识,学习了重载new和delete

2:C++空类的大小是1

3:最让我激动的就是:C++程序把运行结果写入新创建的文档里面,这个和ACM里常用的文件读写还是不一样滴。

好吧,继续努力!!!

时间: 2024-10-27 00:14:24

动态对象创建(二)重载new和delete的相关文章

动态对象创建

C++的动态对象创建 对象创建 当创建一个C++对象时,会发生两件事: (1)为对象分配内存 (2)调用构造函数来初始化那个内存 然而,为对象分配内存可以用以下几种方式或在可选择的时间发生: (1)在静态存储区域,存储空间在程序开始之前就可以分配.这个存储空间在整个运行期间都存在. (2)无论何时到达一个特殊的执行点(左大括号)时,存储单元都可以在栈上被创建.出了执行点(右大括号),这个存储单元自动被释放.这些栈分配运算内置于处理器的指令集中,非常有效.但是,在写程序的时候,必须知道需要多少个存

《C++编程思想》 第十二章 动态对象创建 (习题+解答)

一.相关知识点 重载new和delete 当创建一个new表达式时有两件事发生.首先,使用运算符new分配内存,然后调用构造函数.在delete表达式里,调用析构函数,然后使用运算符delete释放内存.我们永远无法控制构造函数和析构函数的调用(否则我们可能意外地搅乱它们),但可以改变内存分配函数运算符new和delete. 被new和delete使用的内存分配系统是为通用目的而设计的.但在特殊的情形下,它不能满足我们的需要.改变分配系统的原因是考虑效率:我们也许要创建和销毁一个特定的类的非常多

C++ &nbsp; 类工厂实现动态对象创建

看了MFC中的动态创建对象后,感觉动态创建对象算是一种技术吧,理论上说算是一种设计模式.其实创建的原理很明了,把对象的类别名称和其构造函数用容器记录下来,在需要的时候给出创建类别查找容器即可创建对象.当然这里我们需要自定义一个全局的函数指针用来指向类别的构造函数.在这里我们使用类工厂模式,在泪工厂中我们定义一个通用构造函数,将函数指针指向它.下面是参考一位网友思路来整理和摸索的代码: //通用的函数指针 typedef  void *(*CREATE_FUNC)(); //创建类的工厂类 cla

动态创建二维vector数组+指针与引用的区别

二维vectorvector<vector <int> > ivec(m ,vector<int>(n));    //m*n的二维vector 动态创建m*n的二维vector方法一:vector<vector <int> > ivec;ivec.resize(m);for(int i=0;i<m;i++) ivec[i].resize(n); 方法二:vector<vector <int> > ivec;ivec

c++ 动态创建二维数组

为了做一个东西需要用到动态创建二维数组,特此记录. #include <iostream> #include "time.h" using namespace std; int main() { srand((unsigned)time(NULL)); int x_num; int y_num; cout<<"please input x_num:"; cin>>x_num; cout<<"please in

Android学习路线(二十一)运用Fragment构建动态UI——创建一个Fragment

你可以把fragment看成是activity的模块化部分,它拥有自己的生命周期,接受它自己的输入事件,你可以在activity运行时添加或者删除它(有点像是一个"子activity",你可以在不同的activity中重用它).本课将向你展示如何使用Support Libaray继承 Fragment 类来让你的应用能够兼容正在运行Android 1.6的设备. 提示: 如果你决定你的应用需求的最低API级别是11或者更高,那么你不需要使用Support Library,你可以直接使用

深入理解java虚拟机(二)HotSpot Java对象创建,内存布局以及访问方式

内存中对象的创建.对象的结构以及访问方式. 一.对象的创建 在语言层面上,对象的创建只不过是一个new关键字而已,那么在虚拟机中又是一个怎样的过程呢? (一)判断类是否加载.虚拟机遇到一条new指令的时候,首先会检查这个指令的参数是否能在常量池中定位到一个类的符号引用,并且检查这个符号代表的类是否被加载.解析并初始化.如果没有完成这个过程,则必须执行相应类的加载. (二)在堆上为对象分配空间.对象需要的空间大小在类加载完成后便能确定.之后便是在堆上为该对象分配固定大小的空间.分配的方式也有两种:

C语言 动态创建二维数组

/*C语言 如何动态创建二维数组 转化为一维数组申请数组,创建和释放都比较简单 */ #include <stdlib.h> #include <stdio.h> #include <malloc.h> #define RANK 10 #define COLUMN 7 int main() { int i,j; int (*p)[COLUMN]; //动态生成二维数组,指定列数为COLUMN,如果想改,自己该里面 //的参数,如果想定义n行2列就为: p=(int (*

动态创建二维数组

vector创建二维数组 初始化二维数组 vector<vector <int> > ivec(n ,vector<int>(m)); //n*m的二维vector vector<vector <int> > ivec(n , vector<int>(m, 0)); //n*m的二维vector,所有元素为0 动态创建m*n的二维 方法一: vector<vector <int> > ivec; ivec.re