重新认识new

前言

 

感谢大佬: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)后一节会单独拎出

函数原型:

1. void* operator new(std::size_t size);
//分配size字节存储空间,返回指向新分配的存储空间的指针,失败抛出一个bad_alloc异常

 

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

 

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

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

 

3.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 例子,虽然很简单,但是对于理解我认为有很好的帮助

 1 #include <iostream>
 2 #include <new>
 3 
 4 using namespace std;
 5 
 6 class Simon
 7 {
 8 public:
 9     Simon()
10     {
11         cout << "constructed [ " << this << " ]" << endl;
12     }
13 
14 private:
15     int s;
16 };
17 
18 int main(int argc, char** argv)
19 {
20     cout << "1: ";
21     Simon* s1 = new Simon;
22     // 分配内存调用 operator new(sizeof(Simon));
23     //然后构建对象 调用构造函数
24 
25     cout << "2: ";
26     Simon* s2 = new(std::nothrow) Simon;
27     // 分配内存调用 operator new(sizeof(Simon), std::nothrow);
28     // 然后构建对象 调用构造函数
29 
30     cout << "3: ";
31     new(s2) Simon;
32     // 不分配内存调用 operator new(sizeof(Simon), s2);
33     // 在s2的地址空间构建对象(可以从输出结果看出),调用构造函数
34 
35     cout << "4: ";
36     Simon* s3 = (Simon*)operator new(sizeof(Simon));
37     // 只分配内存 
38     // 不构建对象
39 
40     delete s1;
41     delete s2;
42     delete s3;
43 
44     cout << endl;
45     system("pause");
46     return 0;
47 }

 

 

 运行结果:

 

总结:可以看出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(0)
    {
        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(5 * sizeof(animal)); // 申请缓冲器
    animal *a = static_cast<animal *>(p);        // 转换类型
    
    // 2.对象构建
    for (int i = 0; i < 4; i++)
    {
        new(a + i) animal(i);// 调用重载构造
    }
    new(a + 4) animal;    //    也可以调用默认构造

    // 3.使用
    for (int i = 0; i < 5; i++)
    {
        (a + i)->show();
    }

    // 4.销毁对象
    for (int i = 0; i < 5; i++)
    {
        (a + i)->~animal();
    }

    // 5.回收空间
    delete[]p;

    cin.get();
    return 0;
}

 

运行结果:

 

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实际上就是指向一个已经分配好的内存缓冲区的的首地址

 

posted @ 2017-11-14 00:43  SimonKly  阅读(552)  评论(0编辑  收藏  举报