c++面向对象之operator new学习笔记

实际的代码中已经很少见到new的身影了,即使是比较老的c++编译器环境, 也提供了auto_ptr管理内存.
但是c++毕竟贴近底层的几个例证就是对类的封装没有增加什么额外的负担,c++对象模型仍然是一一映射到连续的内存地址上去;
对于内存的分配方式, 控制权仍然在程序员手里. 还是很有必要熟悉理解一番的.

重载operator new好处

  1. 用来检测运行上的错误:比如比较常见的数组越界的段错误, 有时候可能系统不会理解dump掉, 那这个错误就隐藏了.利用这一点可以实现内存泄露.

  2. 提供性能: 这点是最重要的,系统提供的模型内存分配行为一刀切, 而不同的数据对内存分配器的需求都不一样,系统提供的不一定最优.

  3. 收集使用统计数据.

set_new_handler

operator new一样都是<new>提供, 用于在内存分配失败bad_alloc后调用执行.
系统提供的原型如下, 默认的全局的返回指针为nullptr不会干啥或,如果有需要可重载, 或者放在类的内部.

std::new_handler set_new_handler(new_handler);

一般这个指针std::new_handler不会全局赋值, 如果是这样无疑会影响其他模块, 人家好好的内存分配不了最多来个bad_alloc
全局指针一赋值, 就不知道去哪里去执行啥东西了, 所以, 自用自归还就成了基本原则, 基本流程如下:

  1. 获取原始的全局指针new_handler, 存储起来

  2. 设置为新的new_handler使用

  3. 自己用完了(作用域内/class内), 把最开始保存的归还设置给系统.

当然, 这里还涉及多线程的问题, 和当前讨论的不相关, 先不考虑. 这种资源的管理最后归还, 正好可以使用类构造/析构函数来完成. 基本形式如下

class NewHandlerHolder {
public:
    //构造函数和析构函数共同自动完成了资源的管理.
    explicit NewHandlerHolder(std::new_handler nh) 
        : handler(nh) {}
    ~NewHandlerHolder() {
        std::set_new_handler(handler);
    }   
private:
    std::new_handler handler;
};
 
class Widget 
{
public:
    void *operator new(size_t size) throw(std::bad_alloc);
 
private:
    static std::new_handler currentHandler;
};
 
std::new_handler Widget::currentHandler = 
    [](){
        cout<<"Widget: Unable to satisfy request for memory\n";
        set_new_handler(nullptr);
    };  
 
void *Widget::operator new(size_t size) throw(std::bad_alloc) { 
    cout<<"begin Widget::operator new\n";
    NewHandlerHolder h(std::set_new_handler(currentHandler));
    return ::operator new(size);
}

重载对象operator new

简化版只给出了重启了一个operator new形式,按理说,为了避免行为的不一致(有的调用全局有的调用字构建)还是全部重载比较稳妥

class INTType
{     
public: 
    INTType():x(0) {}
    static void *operator new(size_t size) throw(std::bad_alloc);
      
    //...
private:
    int x;
};

重载对象operator new最佳实践

如果系统中有若干个类都有重载全局operator new的需求, 实际上可以新抽象成一个类作为基类, 子类继承接口即可.
而如果要求不仅节省代码, 还要各个类的实例数据独占一份, 可以把类编程模板类,这样子类实例化数据都是独立的.

template<typename T>    // T用于实例化用, 类本身并不使用
class NewHandlerSupport {
public:
    void *operator new(size_t size) throw(std::bad_alloc);
    void *operator new[](size_t size) throw(std::bad_alloc);
 
protected:
    static std::new_handler currentHandler;
};
// 使用类的继承方式
class WidgetHander : public NewHandlerSupport<WidgetHander>

总结

没需求不要重载全局的任何东西,影响范围比较大. 最好放到类中重载
现在对于内存的统计和记录应该都有比较好和成熟的方案, 另外由于智能指针的引入对于内存的使用也很少发生内存泄露等问题
剩下对于operator new重载的强烈需求只来自于性能上的提升, 这方便还是可以继续深入学习一下的boost::Pool

参考: <EC49/50/51/52>

相关推荐