C++ new 解析重载

时间:2022-01-01 20:39:21

C++ new 解析重载

 

new的三种形式: 

(1)operator new(运算符new) 
(2)new operator(new 操作) 
(3)placement new(特殊的new操作)(不分配内存 + 构造函数的调用)

 

operator new 
重载时体现运算符new 
++++++++++++++++++++++++++++++++++++++++++++++++++++++ 
void * opertor new (size_t size ,const char * file ,long line) 

cout << file << “:” << line << endl; 
void *p = malloc(size); 
return p 

+++++++++++++++++++++++++++++++++++++++++

(1)只分配所要求的空间,不调用相关对象的构造函数。当无法满足所要求分配的空间时,则如果有new_handler,则调用new_handler,否则如果没要求不抛出异常(以nothrow参数表达),则执行bad_alloc异常,否则返回回0 
(2) 可以被重载 
(3) 重载时,返回类型必须声明为void* 
(4) 重载时,第一个参数类型必须为表达要求分配空间的大小(字节),类型为size_t 
(5) 重载时,可以带其它参数

 

new operator

String *str = new String(”hello”); 
(1) 调用operator new分配足够的空间,并调用相关对象的构造函数 .
(2) 不可以被重载(作为运算符,new和sizeof一样,是C++内置的,你不能对它做任何的改变,除了使用它。)

 

palacement new

在已经分配的内存(可以是堆或者栈)上重新分配空间,但不会分配新的空间,只是构造函数的调用。 
+++++++++++++++++++++++++++++++++++++++++ 
void * operator new(size_t size,void *p) 

return p; 
}

cahr buffer[1024] 
Test *p2 = new(buffer) Test(200);//operator new(sizo_t ,void *p) 
cout << p2->n_ <

+++++++++++++++++++++++++++++++++++++++++ 


 
 

 C++ new 解析重载

 

最常用的是作为运算符的new,比如:

string *str = new string(“test new”);

作为运算符,new和sizeof一样,是C++内置的,你不能对它做任何的改变,除了使用它。

new会在堆上分配一块内存,并会自动调用类的构造函数。

C++ new用法之二 new函数

第二种就是new函数,其实new运算符内部分配内存使用的就是new函数,原型是:

void *operator new(size_t size);

new函数返回的是一个void指针,一块未经初始化的内存。如你所见,这和C语言的malloc行为相似,你可以重载new函数,并且增加额外的参数,但是必须保证第一个参数必须是size_t类型,它指明了分配内存块的大小,C++允许你这么做,当然一般情况下这是不必要的。如果重载了new函数,在使用new操作符时调用的就是你重载后的new函数了。

如果使用new函数,和语句string *str = new string(“test new”)相对的代码大概是如下的样子:

 

  1. string *str = (string*)operator new(sizeof(string));
  2. str.string(“test new”);
  3. // 当然这个调用时非法的,但是编译器是没有这个限制的

 

这还不算完,还有第三种的new存在。

C++ new用法之三 placement new

第三种,placement new,这也是new作为函数的一种用法,它允许你在一块已存在的内存上分配一个对象,而内存上的数据不会被覆盖或者被你主动改写,placement new同样由new操作符调用,调用格式是:

new (buffer) type(size_t size);

先看看下面的代码:

 

  1. char str[22];
  2. int data = 123;
  3. int *pa = new (&data) int;
  4. int *pb = new (str) int(9);

 

结果*pa = 123(未覆盖原数据),而*pb = 9(覆盖原数据),可以看到placement new 并没有分配新的内存,也可以使用在栈上分配的内存,而不限于堆。

为了使用placement new 你必须包含<new>或者<new.h>

其实placement new和第二种一样,只不过多了参数,是函数new的重载,语法格式为:

void *operator new(size_t, void* buffer);

它看起来可能是这个样子:

void *operator new(size_t, void* buffer) { return buffer;}

 

和new对应的就是delete了,需要回收内存啊,不然就泄漏了,这个下次再写吧,回忆一下今天的内容先。

总结

1. 函数new

void *operator new(size_t size); 在堆上分配一块内存,和placement new(void *operator new(size_t, void* buffer)); 在一块已经存在的内存上创建对象,如果你已经有一块内存,placement new会非常有用,事实上,它STL中有着广泛的使用。

2. 运算符new

最常用的new,没什么可说的。

3. 函数new不会自动调用类的构造函数,因为它对分配的内存类型一无所知;而运算符new会自动调用类的构造函数。

4. 函数new允许重载,而运算符new不能被重载。

5. 紧接着就是对应的delete。


 

new的六种重载形式

 
==========================
当写出
p = new P();
这样的代码的时候, 实际上有两步操作, 首先分配内存,
然后在分配好的内存之上初始化类成员.

第二步是有构造函数完成的, 第一步就是new函数的工作.

全局的new有六种重载形式, 
void *operator new(std::size_t count)
    throw(std::bad_alloc);             //一般的版本

void *operator new(std::size_t count,  //兼容早版本的new
    const std::nothrow_t&) throw();    //内存分配失败不会抛出异常

void *operator new(std::size_t count, void *ptr) throw();
                                       //placement版本
void *operator new[](std::size_t count)  //
    throw(std::bad_alloc);

void *operator new[](std::size_t count,  //
    const std::nothrow_t&) throw();

void *operator new[](std::size_t count, void *ptr) throw();

所以, 刚才的用法, 就是使用new函数的一种重载形式.
如果A这个对象以同样实行重载了new函数的化, 作为成员函数
会被优先调用.
 
C++的各种new简介

1.new T

第一种new最简单,调用类的(如果重载了的话)或者全局的operator new分配空间,然后用
类型后面列的参数来调用构造函数,用法是
new TypeName(initial_args_list). 如果没有参数,括号一般可以省略.例如

int *p=new int;
int *p=new int(10);
int *p=new foo("hello");

通过调用delete来销毁:
delete p;

2. new T[]
这种new用来创建一个动态的对象数组,他会调用对象的operator new[]来分配内存(如果
没有则调用operator new,搜索顺序同上),然后调用对象的默认构造函数初始化每个对象
用法:
new TypeName[num_of_objects];
例如
int *p= new int[10];
销毁时使用operator delete[]

3.new()T 和new() T[]
这是个带参数的new,这种形式的new会调用operator new(size_t,OtherType)来分配内存
这里的OtherType要和new括号里的参数的类型兼容,

这种语法通常用来在某个特定的地址构件对象,称为placement new,前提是operator new
(size_t,void*)已经定义,通常编译器已经提供了一个实现,包含<new>头文件即可,这个
实现只是简单的把参数的指定的地址返回,因而new()运算符就会在括号里的地址上创建
对象

需要说明的是,第二个参数不是一定要是void*,可以识别的合法类型,这时候由C++的重载
机制来决定调用那个operator new

当然,我们可以提供自己的operator new(size_,Type),来决定new的行为,比如
char data[1000][sizeof(foo)];
inline void* operator new(size_t ,int n)
{
        return data[n];
}

就可以使用这样有趣的语法来创建对象:
foo *p=new(6) foo(); //把对象创建在data的第六个单元上
的确很有意思
标准库还提供了一个nothrow的实现:
void* operator new(std::size_t, const std::nothrow_t&) throw();
void* operator new[](std::size_t, const std::nothrow_t&) throw();

就可以实现调用new失败时不抛出异常
new(nothrow) int(10);
// nothrow 是std::nothrow_t的一个实例


placement new 创建的对象不能直接delete来销毁,而是要调用对象的析够函数来销毁对
象,至于对象所占的内存如何处理,要看这块内存的具体来源

4. operator new(size_t)
这个的运算符分配参数指定大小的内存并返回首地址,可以为自定义的类重载这个运算符,
方法就是在类里面声明加上
void *operator new(size_t size)
{
        //在这里分配内存并返回其地址
}
无论是否声明,类里面重载的各种operator new和operator delete都是具有static属性的

一般不需要直接调用operator new,除非直接分配原始内存(这一点类似于C的malloc)
在冲突的情况下要调用全局的operator加上::作用域运算符:
::operator new(1000); // 分配1000个字节

返回的内存需要回收的话,调用对应的operator delete

5.operator new[](size_t)

这个也是分配内存,,只不过是专门针对数组,也就是new T[]这种形式,当然,需要时可以
显式调用

6.operator new(size_t size, OtherType other_value)
和operator new[](size_t size, OtherType other_value)
参见上面的new()


需要强调的是,new用来创建对象并分配内存,它的行为是不可改变的,可以改变的是各种
operator new,我们就可以通过重载operator new来实现我们的内存分配方案.

 

 

new重载注意事项

 
====================
C语言的malloc应用范围很窄,只能从“堆”里取可用内存;当没有资源时,直接返回0,需要大量的错误处理代码来检查这个错误;也不提供任何“回调函数”,应对和处理内存不足;对象的初始化和内存申请需要分成两个语句做。C++的new解决了malloc的不足,甚至还提供的重载机制,给了程序员更多的控制权力。
 
使用和重载new时,也要注意一些问题。
 
1 子模块或库的开发者不要去重载new。
是否重载new必须是在项目经理或架构师统一指挥的行动。因为一个程序只能有一套全局的new。
举个不恰当的比方:行政省自己不能替换货币的汇率,只能由国家层面统一调整货币的汇率。
看看stl和boost,人家老老实实的用系统默认的new和delete,根本没去重载new和delete。
 
2 重载new时要符合“潜规则”
重载了new,delete,不要忘记也重载new[],delete[]。
C++中如果一个类“手工”定义了构造函数,编译器就不会“偷偷的”生成需要的构造函数。
但是重载new,delete不是这样。假设只重载了delete,忘记delete[],编译器不会告诉你这个危险。
当私家制做的new[]分配的内存p,被交给系统默认的delete[]时,程序可能崩溃。
还有就是new,delete要般配,要抛bad_alloc,要回调用new_handle等等。可参考effective c++.
 
3 重载new,使用boost内存池。
好像这是个死套,boost内存池里的内存在默认情况下是new出来的,而new要从boost池里申请,死循环了。
其实不然。boost从没有假定,自己的内存池的空间来源是new出来的。boost内存池只是使用了默认的内存分配器,这个默认分配器是用的new。我们可以改变UserAllocator这个模板参数。
 
4 重载new,STL容器
STL容器的默认分配器是调new和delete的。当想解决STL容器造成的内存碎片问题时,有两个基本的办法。
一个办法是提供STL容器专用的分配器,这个办法需要改大量的代码。
map<int,int,less<int>,MyAlloc> m_map; //....这种代码会大量出现。
第二个办法就是重载new。如果map默认是用new分配的,可以不用改代码。
 
5 重载new,COM
微软的COM技术使用了大量继承和琐碎对象,如果不重载new,COM造成的小内存块将是很惊人的。
例如ADO,调一圈ADO,产生无数new和delete操作。
 
6 重载new,其他特殊动机
检测代码中的内存错误:可以在分配的内存块上加上小cookey(附加隐藏的走私货),以检查越界问题。
优化性能:从全局静态区等特殊区域获得内存,甚至可以消除锁开销(单线程环境下)。
获得内存使用的统计数据:简单的计数,统计,报告。
测试:假设您想模拟一个bad_alloc例外抛出的环境,不用去分配1000000000字节了,直接throw一个得了。
 
7 内存对齐
建议所有内存块开始从4字节的整数倍地址值开始对齐。特别是从自定义内存池分块得到的内存块。