zoukankan      html  css  js  c++  java
  • C++ new/new operator、operator new、placement new初识

    简要释义

    1.operator new是内存分配函数(同malloc),C++在全局作用域(global scope)内提供了3份默认的operator new实现,并且用户可以重载operator new。

    1 void* operator new(std::size_t) throw(std::bad_alloc);//normal new
    2 void* operator new(std::size_t,const std::nothrow_t&) throw();//nothrow new
    3 void* operator new(std::size_t,void*) throw();//placement new

    下面这两行代码是等价的,都是分配一块大小为sizeof(widget)的内存并返回指针,没有执行构造函数

    1 widget *a=(widget*) ::operator new(sizeof(widget));
    2 widget *b=(widget*)malloc(sizeof(widget));

    2.new/new operator即C++内置的new操作符。

    //这里new一个widget对象分成两步
    //1.运行期系统调用operator new开辟sizeof(widget)大小的内存
    //2.在该内存地址上构造一个widget对象
    widget *c=new widget();

    我们平常的new操作由运行期系统调用operator new,然后调用构造函数初始化。这个过程是不可重定义的。即程序员不能重载C++内建的new操作符。我们能重载的仅是其中的 operator new/operator new[],即分配内存的部分。

    3.placement new是operator new在全局作用域上的一个重载版本,即如上我们看到的

    1 void* operator new(std::size_t,void*) throw();//placement new

     placement new并不分配内存,而是返回已分配的内存的指针,这个指针正是函数参数列表中的void *,即“返回一个你刚传入的已分配的内存的指针”。

    std::中该函数的实现:

    1 inline _LIBCPP_INLINE_VISIBILITY void* operator new  (std::size_t, void* __p) _NOEXCEPT {return __p;}

     那么为什么需要这个placement new呢?

    答案是:当你需要在一段已分配的内存中构造对象时,调用寻常的new widget()会开辟另外一个内存空间,而非在已知地址上构造widget()对象。

    //这里先申请了一段内存空间,由指针widget*a持有,并未调用构造函数
    //然后用placement new在a地址上构造了widget对象
    //这里::表示调用在global scope中的匹配函数
    widget *a=(widget*) ::operator new(sizeof(widget));
    ::new(a) widget();

    进一步的讨论

    1.placement new实质上是有额外实参之operator new,一般情况下我们指的placement new是那个额外实参为void*的重载版本(已被纳入C++标准程序库),这些叫法对我们的讨论没有影响,只要知道placement new同时也是一种operator new即可。但是不要忘了我们可以重载其它版本的placement new,例如额外实参为std::ostream&,提供log功能。

     1 #include <iostream>
     2 class widget
     3 {
     4 public:
     5     static void * operator new(std::size_t _size,std::ostream& o)
     6     {
     7         o<<"void * operator new(std::size_t _size,ostream& o)"<<std::endl;
     8         return ::operator new(_size);
     9     }
    10     widget()
    11     {
    12         std::cout<<"widget()"<<std::endl;
    13     };
    14     ~widget()
    15     {
    16         std::cout<<"~widget()"<<std::endl;
    17     };
    18 
    19 };
    20 int main()
    21 {
    22     //下面两种构造方法是等价的
    23     //构造一个widget对象,并且使用有log功能的operator new
    24     widget* a=(widget*) widget::operator new(sizeof(widget), std::cout);
    25     ::new(a) widget();
    26     
    27     //同样构造一个widget对象,并且使用有log功能的operator new
    28     widget* b=new(std::cout) widget();
    29     return 0;
    30 }

    这份示例代码中,我在类中重载了placement new,附带的额外参数是ostream&。

    2.注意作用域遮掩问题。如果你在类内重载了一个operator new,当你对这个类及其子类使用new操作符的时候,会掩盖全局作用域中的operator new,编译器发现里层作用域(类内)有operator new声明,就不会查找全局作用域是否有其它operator new声明,而是直接进入参数匹配阶段,如果你重载的operator new参数和调用的不匹配,便会抛出一个编译错误。

    解决方法就是:如果类内重载了operator new,并且你仍有可能使用到全局作用域中的operator new,请同时也重载和全局作用域同型的operator new,确保调用成功。

    为你的类建立一个base class,内含全局作用域同型的operator new,使其调用全局作用域内的operator new即可。

     1 #include <iostream>
     2 class globalScopeNew
     3 {
     4 public:
     5     static void* operator new(std::size_t size) throw(std::bad_alloc)
     6     {
     7         return ::operator new(size);
     8     }
     9     static void* operator new(std::size_t size,const std::nothrow_t& t) throw()
    10     {
    11         return ::operator new(size, t);
    12     }
    13     static void* operator new(std::size_t size,void* p) throw()
    14     {
    15         return ::operator new(size, p);
    16     }
    17     
    18 };
    19 class widget:public globalScopeNew
    20 {
    21 public:
    22     using globalScopeNew::operator new;
    23     static void * operator new(std::size_t _size,std::ostream& o)
    24     {
    25         o<<"void * operator new(std::size_t _size,ostream& o)"<<std::endl;
    26         return ::operator new(_size);
    27     }
    28     widget()
    29     {
    30         std::cout<<"widget()"<<std::endl;
    31     };
    32     ~widget()
    33     {
    34         std::cout<<"~widget()"<<std::endl;
    35     };
    36    
    37 };
    38 int main()
    39 {
    40     widget* w2=new(std::cout) widget();
    41     //这句调用原来不能通过编译
    42     widget* w1=new widget();
    43     widget* w3=(widget*)operator new(sizeof(widget));
    44     //这句调用原来不能通过编译
    45     new(w3) widget();
    46     return 0;
    47 }

    注意,这边需要在子类中using globalScopeNew::operator new;即在子类中使基类的operator new可见。

    这样,各种形式的new操作符调用都能通过编译了。

  • 相关阅读:
    程序员如何在百忙中更有效地利用时间,如何不走岔路,不白忙(忙得要有效率,要有收获)
    最坏的不是面试被拒,而是没面试机会,以面试官视角分析哪些简历至少能有面试机会
    最近面了不少java开发,据此来说下我的感受:哪怕事先只准备1小时,成功概率也能大大提升
    Ribbon整合Eureka组件,以实现负载均衡
    时间对于程序员的价值,以及如何高效地利用时间,同时划分下勤奋度的等级
    面试过程中,可以通过提问环节的发挥,提升面试的成功率
    以技术面试官的经验分享毕业生和初级程序员通过面试的技巧(Java后端方向)
    和小鲜肉相比,老程序员该由哪些优势?同时说下我看到的老程序员的三窟
    通过软引用和弱引用提升JVM内存使用性能的方法(面试时找机会说出,一定能提升成功率)
    Spring Clould负载均衡重要组件:Ribbon中重要类的用法
  • 原文地址:https://www.cnblogs.com/kyokuhuang/p/4199724.html
Copyright © 2011-2022 走看看