前言

感谢大佬:https://www.cnblogs.com/luxiaoxun/archive/2012/08/10/2631812.html

www.cplusplus.com

因为这段时间在重新再次学习STL,在学习到deque时,遇到了allocator类,学习过程中又遇到operator new,发现现在我认识的new,已经不是我之间认识的那个new了,故我要好好认清她。

之前从C转向C++,因此免不了的就是malloc / free 和 new / delete对比。

1. malloc与free是C++/C语言的标准库函数,new/delete是C++的运算符。它们都可用于申请动态内存和释放内存。

2. 对于内部数据类型(int, char, double...)的对象而言,光用malloc / free已经可以满足动态对象的要求。而在非内部数据类型(即类)对象在创建的同时要自动执行构造函数,对象在消亡之前要自动执行析构函数,这时候malloc / free已经无法满足,这时候new / delete应允而生。

而在面试中也会经常被问到,但是现在来看我了解还是皮毛。。。

如果读到这的话,写的可能比较冗余 请耐心看下去  谢谢!!!

new operator(delete operator) & operator new (operator delete)

首先我们得弄清楚 new operator(delete operator) &  operator new (operator delete)它们不一样,不一样。。。

new operator (delete operator) 就是new (delete)操作符,而operator new (operator delete)是函数

new operator

(1)调用operator new分配足够的空间,并调用相关对象的构造函数
(2)不可以被重载

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

operator new 函数

请先注意operator new的version (1)、(2),对于version(3)后一节会单独拎出

函数原型:

. void* operator new(std::size_t size);
//分配size字节存储空间,返回指向新分配的存储空间的指针,失败抛出一个bad_alloc异常
.void* operator new(std::size_t size, const std::nothrow_t& nothrow_value);
//与第一个相同,只是在内存分配失败时,不会抛出异常,而是返回一个null指针

对于nothrow的相关东西,请移步于我的另一篇博文

http://www.cnblogs.com/SimonKly/p/7826660.html

.void* operator new(std::size_t size, void* ptr) throw(); //placement new
//简单返回ptr,不会分配空间

这个版本的operator new重载方式,不同于前两个版本,而是单独拿出来的,被叫做placement new。是最难理解(我比较笨,琢磨了好久)

默认的分配和释放函数是标准库中特殊组件,有以下性质:
* 全局性: operator new三个版本都在全局命名空间声明
* 隐式声明: 无论是否包含头<new>,分配版本((1)和(2))在C++程序的每个翻译单元中隐式声明。
* 可重载: 分配版本((1)和(2))也是可替换的:程序可以提供自己的定义来替换默认提供的定义来产生上述结果,或者可以为特定类型重载。

下面有一个 http://www.cplusplus.com 例子,虽然很简单,但是对于理解我认为有很好的帮助

 #include <iostream>
#include <new> using namespace std; class Simon
{
public:
Simon()
{
cout << "constructed [ " << this << " ]" << endl;
} private:
int s;
}; int main(int argc, char** argv)
{
cout << "1: ";
Simon* s1 = new Simon;
// 分配内存调用 operator new(sizeof(Simon));
//然后构建对象 调用构造函数 cout << "2: ";
Simon* s2 = new(std::nothrow) Simon;
// 分配内存调用 operator new(sizeof(Simon), std::nothrow);
// 然后构建对象 调用构造函数 cout << "3: ";
new(s2) Simon;
// 不分配内存调用 operator new(sizeof(Simon), s2);
// 在s2的地址空间构建对象(可以从输出结果看出),调用构造函数 cout << "4: ";
Simon* s3 = (Simon*)operator new(sizeof(Simon));
// 只分配内存
// 不构建对象 delete s1;
delete s2;
delete s3; cout << endl;
system("pause");
return ;
}

运行结果:

总结:可以看出version (1)、(2)只是分配存储空间,而不构建对象 ,new operator分配空间时,调用version(1)or (2)来进行自定义化内存分配

Notice:
operator new可以显式调用为常规函数,但在C++中,new是具有非常特定行为的运算符:具有new运算符的表达式首先调用具有其类型说明符大小的函数operator new() 作为第一个参数,如果这是成功的,它会自动初始化或构造对象(如果需要的话)。

为什么有必要写自己的operator new和operator delete?why?(其实我也不知道,,,)
  答案通常是:为了效率。缺省的operator new和operator delete具有非常好的通用性,它的这种灵活性也使得在某些特定的场合下,可以进一步改善它的性能。尤其在那些需要动态分配大量的但很小的对象的应用程序里,情况更是如此。具体可参考《Effective C++》中的第二章内存管理。

placement new

placement new其实就是operator new重载第三个版本,也不知道谁为什么叫其placement new,害的我还以为是一个新东西,之后发现就是 http://www.cplusplus.com (见下图)上,曰其为 placement 版本。。。

void* operator new(size_t size, void* p) throw()
{
return p;
}

placement new的执行忽视size_t参数,只返回还第二个参数。允许用户把一个对象放到一个指定的内存缓冲器中,参数p它就指向一个内存缓冲器。

placement new使用步骤

placement new主要适用于:
在对时间要求非常高的应用程序中,因为这些程序分配的时间是确定的;长时间运行而不被打断的程序,以及执行一个垃圾收集器(GC, garbage collector)

在很多情况下,placement new的使用方法和其他普通的new有所不同。这里提供了它的使用步骤。

第一步 缓存提前分配

有三种方式:

1.为了保证通过placement new使用的缓存区的memory alignment(内存队列)正确准备,使用普通的new来分配它:在堆上进行分配
class Task ;
char * buff = new [sizeof(Task)]; //分配内存
(请注意auto或者static内存并非都正确地为每一个对象类型排列,所以,你将不能以placement new使用它们。)

2.在栈上进行分配
class Task ;
char buf[N*sizeof(Task)]; //分配内存

3.还有一种方式,就是直接通过地址来使用。(必须是有意义的地址)
void* buf = reinterpret_cast<void*> (0xF00F);

第二步:对象的分配

在刚才已分配的缓存区调用placement new来构造一个对象。
Task *ptask = new (buf) Task

第三步:使用

按照普通方式使用分配的对象:

ptask->memberfunction();

ptask-> member;

//...

第四步:对象的析构

一旦你使用完这个对象,你必须调用它的析构函数来销毁它。按照下面的方式调用析构函数:
ptask->~Task(); //调用外在的析构函数

这里是不是就像GC机制呢?

你可以反复利用缓存并给它分配一个新的对象(重复步骤2,3,4),节省了申请空间的时间开销。

第五步:释放

如果你不打算再次使用这个缓存,你可以象这样释放它:delete [] buf;

跳过任何步骤就可能导致运行时间的崩溃,内存泄露,以及其它的意想不到的情况。如果你确实需要使用placement new,请认真遵循以上的步骤。

如下有一个简单程序:

#include <iostream>

using namespace std;

class animal
{
public:
#if 1 //用于演示,无默认构造函数
animal() : num()
{
cout << "animal constructor default" << endl;
}
#endif
animal(int _num) : num(_num)
{
cout << "animal constructor param" << endl;
} ~animal()
{
cout << "animal destructor" << endl;
} void show()
{
cout << this->num << endl;
} void * operator new(size_t size, void *p)
{
return p;
} private:
int num;
}; int main(int args, char ** argv)
{
// 一个动态animal数组
void *p = operator new( * sizeof(animal)); // 申请缓冲器
animal *a = static_cast<animal *>(p); // 转换类型 // 2.对象构建
for (int i = ; i < ; i++)
{
new(a + i) animal(i);// 调用重载构造
}
new(a + ) animal; // 也可以调用默认构造 // 3.使用
for (int i = ; i < ; i++)
{
(a + i)->show();
} // 4.销毁对象
for (int i = ; i < ; i++)
{
(a + i)->~animal();
} // 5.回收空间
delete[]p; cin.get();
return ;
}

运行结果:

placement new 存在的理由

1.用placement new 解决buffer的问题

问题描述:用new分配的数组缓冲时,由于调用了默认构造函数,因此执行效率上不佳。若没有默认构造函数则会发生编译时错误。如果你想在预分配的内存上创建对象,用缺省的new操作符是行不通的。要解决这个问题,你可以用placement new构造。它允许你构造一个新对象到预分配的内存上。

2.增大时空效率的问题

使用new操作符分配内存需要在堆中查找足够大的剩余空间,显然这个操作速度是很慢的,而且有可能出现无法分配内存的异常(空间不够)。placement new就可以解决这个问题。我们构造对象都是在一个预先准备好了的内存缓冲区中进行,不需要查找内存,内存分配的时间是常数;而且不会出现在程序运行中途出现内存不足的异常。所以,placement new非常适合那些对时间要求比较高,长时间运行不希望被打断的应用程序。

new 、operator new 和 placement new 区别

(1)new :不能被重载,其行为总是一致的。它先调用operator new分配内存,然后调用构造函数初始化那段内存。

  new 操作符的执行过程:
    1. 调用operator new分配内存 ;
    2. 调用构造函数生成类对象;
    3. 返回相应指针。

(2)operator new:只分配内存空间,要实现不同的内存分配行为,应该重载operator new,而不是new。

operator new就像operator + 一样,是可以重载的。如果类中没有重载operator new,那么调用的就是全局的::operator new来完成堆的分配。同理,operator new[]、operator delete、operator delete[]也是可以重载的。

(3)placement new:只是operator new重载的一个版本。它并不分配内存,只是返回指向已经分配好的某段内存的一个指针。因此不能删除它,但需要调用对象的析构函数。

如果你想在已经分配的内存中创建一个对象,使用new时行不通的。也就是说placement new允许你在一个已经分配好的内存中(栈或者堆中)构造一个新的对象。原型中void* p实际上就是指向一个已经分配好的内存缓冲区的的首地址

随机推荐

  1. 【python】 判断纯ascii串

    参考:http://stackoverflow.com/questions/3636928/test-if-a-python-string-is-printable print all(ord(c)& ...

  2. 基于Netty重构RPC框架

    下面的这张图,大概很多小伙伴都见到过,这是Dubbo 官网中的一张图描述了项目架构的演进过程.随着互联网的发展,网站应用的规模不断扩大,常规的垂直应用架构已无法应对,分布式服务架构以及流动计算架构势在 ...

  3. Controller的返回值

    public String editItems(Model model) throws Exception { //itemsQueryVo参数如果没有的话 可以传一个null ItemsCustom ...

  4. ThreadLocal和单例对象比较

    单例对象: 自始至终只有一个对象 当线程并发,每个线程需要自己独立的资源变量处理不同的业务时,单例对象远远不能满足需求 因此可以采用ThreadLocal模式 : 每个线程有自己独立的资源变量     ...

  5. js实现千位符分隔

    前几天面试做保险项目的公司,被问到了一道实现千位符分割方法的题,乍一看挺简单,但做起来最后却没给出来一个合适的解决方法.回来自己琢磨了一个还行的答案. var num = 3899000001, ar ...

  6. Node总结

    一. 基本概念 1.1 DOM DOM(Document Object Model), 把网页转换成JS对象,可以用脚本进行各种操作.浏览器将结构化文档(HTML/XML)解析成一系列的节点形成DOM ...

  7. k3 cloud移动审批提示实体类型BD_TaxRate中不存在名为AmountDigits属性

    原因是由于字段没有正确绑定币别,找到对应的字段并修改绑定币别

  8. JavaScript面向对象编程(2)-- 类的定义

    最近这一段时间事情太多了,没有时间再继续写,幸好这两天有点小闲,先小写一下JavaScript中面向对象一中推荐的方法.本文承接上一篇JavaScript面向对象编程(1) -- 基础. 上篇说过,J ...

  9. MySQL--17 配置binlog-server 及中间件

    目录 配置binlog-server MySQL中间件Atlas Atlas管理接口 配置binlog-server 修改mha配置文件 [root@mysql-db03 ~]# vim /etc/m ...

  10. 分布式理论 BASE、CAP、ACID

    CAP原理: 在理论计算机科学中,CAP定理(CAP theorem),又被称作布鲁尔定理(Brewer's theorem),它指出对于一个分布式计算系统来说,不可能同时满足以下三点: 一致性(Co ...