• C++ new 解析重载


    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字节的整数倍地址值开始对齐。特别是从自定义内存池分块得到的内存块。

     
    参考链接:
  • 相关阅读:
    10.28
    10.25
    10.21
    移动第七次作业
    移动第六次作业
    移动第五次作业
    移动第四次作业
    移动第3次作业
    移动第二次作业
    移动第一次作业
  • 原文地址:https://www.cnblogs.com/2018shawn/p/13963381.html
Copyright © 2020-2023  润新知