• Effective C++ 第二版 8) 写operator new 和operator delete 9) 避免隐藏标准形式的new


    条款8 写operator new 和operator delete 时要遵循常规

    重写operator new时, 函数提供的行为要和系统缺省的operator new一致:

    1)正确的返回值; 2)可用内存不够时调用出错处理函数; 3)处理0字节内存请求的情况; 避免隐藏标准形式的new;

    1)如果内存分配请求成功, 返回指向内存的指针, 失败抛出std::bad_alloc异常; 
    operator new实际上不止一次尝试分配内存, 每次失败会调用出错处理函数(期望释放别处的内存), 只有在出错处理函数的指针为空的情况下才抛出异常.

    Note 按C++标准要求, 在请求分配0字节的内存时, operator new也要返回一个合法指针.

    非类成员形式的operator new伪代码:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    void  * operator  new ( size_t  size)  // operator new 还可能有其它参数
    {
         if  (size == 0) {  // 处理0 字节请求时,
             size = 1;  // 把它当作1 个字节请求来处理
         }
         while  (1) {
             "分配 size 字节内存;"
             if  (分配成功)
                 return  (指向内存的指针);
             // 分配不成功,找出当前出错处理函数
             new_handler globalHandler = set_new_handler(0);
             set_new_handler(globalHandler);
             if  (globalHandler) (*globalHandler)();
             else  throw  std::bad_alloc();
         }
    }

    >处理零字节的请求的技巧是把他作为请求一个字节来处理;

    >把handler置为0然后再恢复是因为没法直接得到handler的指针, 必须调用set_new_handler;

    >operator new包含一个无限循环: while(1), 跳出循环的条件是内存分配成功或出错处理函数完成事件中的一种: 
    得到可用内存; 安装了新的new-handler; 卸除了new-handler; 抛出bad_alloc类型的异常; 返回失败; 所以new-hander必须做到其中一件事, 否则循环无法结束;

    operator new经常会被子类继承, 引出复杂度; 大多数指针对类所写的operator new只为特定的类设计的, 不是为其他类或子类设计的;
    对于一个类X的operator new来说, 函数内部的行为在涉及到对象的大小时, 都是sizeof(X). 但由于存在继承, 基类中的operator new可能被调用给子类对象分配内存;

    1
    2
    3
    4
    5
    6
    7
    8
    class  Base {
         public :
         static  void  * operator  new ( size_t  size);
    ...
    };
    class  Derived:  public  Base  // Derived 类没有声明operator new
    { ... };  //
    Derived *p =  new  Derived;  // 调用Base::operator new

    >如果Base类的operator new不想处理这种情况, 简单的方法是把内存分配请求转给标准operator new来处理:

    1
    2
    3
    4
    5
    6
    7
    void  * Base::operator  new ( size_t  size)
    {
         if  (size !=  sizeof (Base))  // 如果数量“错误”,让标准operator new
             return  ::operator  new (size);  // 去处理这个请求
    //
    ...  // 否则处理这个请求
    }

    >size != sizeof(Base)处理了size等于零的情况: C++标准规定独立的freestanding类的大小都是非零值; 即使Base没有成员, sizeof(Base)也总是非0; (非独立的类sizeof可能为零) [嵌套类??] size为零时, 请求就会转到::operator new来处理;

    如果想控制基于类的数组的内存分配, 必须实现operator new的数组形式: operator new[]("数组new");
    写operator new[]时, 面对的是"原始"内存, 不能对数组里还不存在的对象进行操作; 还不知道数组对象的个数和大小;
    基类的operator new[]会通过继承的方式被用来为子类对象的数组分配内存, 但子类对象一般比基类的大;
    Base::operator new平[]里的每个对象大小不一定都是sizeof(Base), 数组对象的数量不一定就是 (请求字节数)/sizeof(Base);

    对于operator delete和operator delete[], 要记住C++保证删除空指针总是安全的;

    1
    2
    3
    4
    5
    6
    7
    void  operator  delete ( void  *rawMemory)
    {
         if  (rawMemory == 0)
             return ; file: //如果指针为空,返回
         //释放 rawMemory指向的内存;
         return ;
    }

    假设类的operator new将"错误"大小分配请求转给::operator new, 那么必须将"错误"大小的删除请求转给::operator delete;

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    class  Base {  // 和前面一样,只是这里声明了operator delete
    public :
         static  void  * operator  new ( size_t  size);
         static  void  operator  delete ( void  *rawMemory,  size_t  size);
    ...
    };
    void  Base::operator  delete ( void  *rawMemory,  size_t  size)
    {
         if  (rawMemory == 0)  // 检查空指针
             return ;
         if  (size !=  sizeof (Base))
         // 如果size"错误",让标准operator 来处理请求
             ::operator  delete (rawMemory);
             return ;
         }
         "释放指向 rawMemory的内存;"
         return ;
    }

    必须遵守operator new和operator delete的规定; 需要内存分配程序支持new-handler函数, 并正确处理零内存请求; 

    条款9 避免隐藏标准形式的new

    内部范围声明的名称会隐藏掉外部范围的相同名称, 所以对于在类的内部和全局声明的同名函数f来说, 类成员函数会隐藏掉全局函数;

    1
    2
    3
    4
    5
    6
    7
    8
    void  f();  // 全局函数
    class  X {
    public :
         void  f();  // 成员函数
    };
    X x;
    f();  // 调用 f
    x.f();  // 调用 X::f

    >调用全局函数和成员函数时采用的是不同的语法形式;

    但是如果在类里增加一个带多个参数的operator new函数:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    class  X {
    public :
         void  f();
    // operator new 的参数指定一个 new-hander(new 的出错处理)函数
         static  void  * operator  new ( size_t  size, new_handler p);
    };
    void  specialErrorHandler();  // 定义在别的地方
     
    X *px1 =  new  (specialErrorHandler) X;  // 调用X::operator new
    X *px2 =  new  X;  //错误

    >类里定义了"operator new"函数后, 会阻止对标准new的访问;

    Solution 1)在类里写一个支持标准new调用方式的operator new, 和标准new做同样的事情;

    1
    2
    3
    4
    5
    6
    7
    8
    9
    class  X {
    public :
         void  f();
         static  void  * operator  new ( size_t  size, new_handler p);
         static  void  * operator  new ( size_t  size) {  return  ::operator  new (size); }
    };
     
    X *px1 =  new  (specialErrorHandler) X;  // 调用 X::operator new(size_t, new_handler)
    X* px2 =  new  X;  // 调用 X::operator new(size_t)

    >使用内联实现;

    Solution 2)为每一增加到operator new的参数提供缺省值;

    1
    2
    3
    4
    5
    6
    7
    class  X {
    public :
         void  f();
         static  void  * operator  new ( size_t  size, new_handler p = 0);  // p 缺省值为0
    };
    X *px1 =  new  (specialErrorHandler) X;  // 正确
    X* px2 =  new  X;  // 也正确

    >以后想对"标准"形式的new定制新的功能, 只需重写这函数;

  • 相关阅读:
    经典问题之生产者-消费者问题——Lock实现
    【转】面试中常见的二叉树题目
    【转】ConcurrentHashMap之实现细节
    【转】java中关键字volatile的作用
    WeakReference 与 SoftReference 区别
    git学习笔记
    android项目笔记整理(3)
    android项目笔记整理(2)
    Android项目笔记整理(1)
    Android实习结束后的阶段性总结
  • 原文地址:https://www.cnblogs.com/riskyer/p/3315388.html
Copyright © 2020-2023  润新知