Effective C++笔记(11)—定制new和delete

Posted NearXDU

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Effective C++笔记(11)—定制new和delete相关的知识,希望对你有一定的参考价值。

定制new和delete


这部分主要介绍Operator newOperator delete

回顾一下,一直熟知的new/delete其实全称是new operator/delete operator是操作符。以new例,它包含了两个操作:第一部分是分配足够的内存以便容纳所需类型的对象。第二部分是它调用构造函数初始化内存中的对象。new操作符总是做这两件事情,你不能以任何方式改变它的行为。

条款49:了解new-handler的行为

operator new 无法满足某一内存分配需求的时候,它会抛出异常。
再抛出异常之前,它会先调用一个客户指定的错误处理函数即new-handler

namespace std
    typedef void (*new_handler)();
    new_handler set_new_handler(new_handler p) throw();
;

可以看出new_handler是一个函数指针类型,该函数指针指向的函数返回空参数也为空。而set_new_handler的操作是从参数设置一个新的new_handler并返回一个旧的new_handler

在STL中有set_new_handler(0);就是强制卸载异常处理函数,当内存分配不够时直接抛出std::bad_alloc异常。

void outOfMem()

    std::cerr << "out of mem" << endl;
    throw bad_alloc();
    //std::abort();

int main()


    std::set_new_handler(outOfMem);
    while (true)
    
        int *p = new int[100000];
    
    
    system("pause");
    return 0;

设计良好的new-handler,本条款给出以下结论:
1.让更多内存可被使用
2.安装另一个new-handler(调用set_new_handler
3.写出new-handler:set_new_handler(0);
4.抛出异常:throw std::bad_alloc();
5.直接std::abort()

首先是一个RAII管理的例子。

class NewHandlerHolder :public boost::noncopyable

public:
    explicit NewHandlerHolder(new_handler nh) :handler(nh)
    ~NewHandlerHolder() set_new_handler(handler); 
private:
    new_handler handler;

;
class Widget
public:
    static std::new_handler set_new_handler(std::new_handler p) throw();
    static void* operator new (std::size_t size) throw(std::bad_alloc);
private:
    static std::new_handler currentHandler;
;
//static 成员在class之外进行初始化
std::new_handler currentHandler = 0;

std::new_handler Widget::set_new_handler(std::new_handler p)throw()

    new_handler oldHandler = currentHandler;
    currentHandler = p;
    return oldHandler;

void *Widget::operator new(size_t size) throw(bad_alloc)

    NewHandlerHolder h(std::set_new_handler(currentHandler));
    return ::operator new(size);

之后作者用了模板对该例子进行了扩充:

///1.RAII
class NewHandlerHolder :public boost::noncopyable

public:
    explicit NewHandlerHolder(new_handler nh) :handler(nh)
    ~NewHandlerHolder() set_new_handler(handler); 
private:
    new_handler handler;

;

//模板
template<class T>
class NewHandlerSupport

public:
    static std::new_handler set_new_handler(std::new_handler p) throw();
    static void* operator new (std::size_t size) throw(std::bad_alloc);
private:
    static std::new_handler currentHandler;
;
template<class T>
std::new_handler NewHandlerSupport<T>::set_new_handler(std::new_handler p)throw()

    new_handler oldHandler = currentHandler;
    currentHandler = p;
    return oldHandler;

template<class T>
void *NewHandlerHolder::operator new(size_t size) throw(bad_alloc)

    NewHandlerHolder h(std::set_new_handler(currentHandler));
    return ::operator new(size);

使用的时候只需要继承于模板类即可:

class Widget : public NewHandlerSupport<Widget>
;

这里的类型参数T只是用来区分Derived class,由于模板的机制,会为每个T生成一份currentHandler

这种技术叫做:CRTP(curiously recurring template pattern)怪异的循环模板模式。

条款50:了解new和delete的合理替换时机

什么时候替换operator new或者operator delete
书中给了几个理由:

1.检测运用时的错误

new/delete要配合使用否则会内存泄漏;
delete大于1次则会造成不定义行为。
另外还有可能有overrun和underrun错误。
overrun:写入点在分配区块尾端之后
underrun:写入点在分配区块起点之前

2.强化效能

定制版的operator new/operator delete性能胜过缺省版本,需要的内存也比较少,最高可以省50%。

3.收集使用上的统计数据

使用定制版的Operator new/operator delete还可以获取到一些信息:比如说分配区块的大小分布?寿命?倾向于FIFO或者是LIFO等等。。

书中给出一个例子,比如说,我们希望在operator new时在申请的空间加入两个int字节存放签名:

第一个强转直接赋值;
第二个强转是先计算signature的存放位置,再将值赋进去。

static const int signature = 0xDEADBEEF;
typedef unsigned char Byte;
void *operator new(std::size_t size) throw(std::bad_alloc)

    using namespace std;
    size_t realSize = size + 2 * sizeof(int);
    void *pMem = malloc(realSize);
    if (!pMem)
        throw bad_alloc();
    *(static_cast<int*>(pMem)) = signature;
    *(reinterpret_cast<int*>(static_cast<Byte*>(pMem)+realSize - sizeof(int))) = signature;
    return static_cast<Byte*>(pMem)+sizeof(int);

条款51:编写new和delete时需要固守的常规

当new_heandler指向null时,抛出异常,否则每次失败后都会调用new-handling函数。

void *operator new(std::size_t size) throw (std::bad_alloc)

    using namespace std;
    if (size == 0)
        size = 1;
    while (true)
    
        void * p = malloc(size);
        if (p!=nullptr)//success
        
            return p;
        
        //作者这样做的意图是获取当前的handler,不过在C++11标准中有get_new_handler这样的调用了。
        new_handler globalHandler = set_new_handler(0);//先拿到当前的handler
        set_new_handler(globalHandler);//set当前的handler为newhandler

        if (globalHandler)
            (*globalHandler)();
        else//直到Handler 为 null 抛出异常。
            throw bad_alloc();

    

上述情况为考虑类继承的情况,假设我们为base类定义了一个Operator new当derived类没有定义自己的operator new那么这个函数将被继承下来,由于基类和继承类某些不一样的特性,这样的调用就会出现问题:

class Base

public:
    static void * operator new (std::size_t size) throw(std::bad_alloc)
    
        cout << "base::new" << endl;
        return nullptr;
    
;
class Derived :public Base


;
int main()

    Derived *p = new Derived;// base::new
    if(p==nullptr)
        cout<<"worked"<<endl;//ok
    system("pause");
    return 0;

如果Base class专属的operator new并非被设计用来对付上述情况,处理此情形的最佳做法是,当内存申请错误,改调用标准的Operator new

class Base

public:
    static void * operator new (std::size_t size) throw(std::bad_alloc)
    
        if (size != sizeof(Base))
        
            return ::operator new(size);
        
        else
        
            cout << "base::new" << endl;
            return nullptr;
        
    
;
class Derived :public Base

    int x;
;
int main()

    Derived *p = new Derived;// base::new
    if (p == nullptr)
        cout << "custom worked" << endl;//ok
    else
        cout << "standard work" << endl;
    system("pause");
    return 0;

比起operator new,operator delete情况更加简单。

void operator delete(void *m)throw()

    if(m==0) return;
    free m;

在class中设计也是类似:

static void operator delete(void *rawMem, std::size_t size)throw()
    
        if (rawMem == 0)return;
        if (size != sizeof(Base))
        
            ::operator delete(rawMem);
            return;
        
    //  归还
        return;
    

与operator new类似,class专属的delete将大小有误的释放行为,转交给标准的delete。
当base没有虚析构函数,C++传给operator delete的大小可能不正确,就有可能调用标准的delete函数,operator delete可能就无法正确运作了。

条款52:写了placement new也要写placement delete

Widge *pw=new Widge;

上述调用分为两部分:
1.分配内存Operator new
2.是调用构造函数

如果构造函数抛出异常,需要将第一步分配的内存释放,否则会资源泄漏。
对于正常形式的new/delete C++运行期系统将会搞定。

如果开始声明了非正常形式的operator new,事情就变得复杂了。

在以前学习STL中,对于placement new的认为是,在以申请的的内存进行初始化,例如:

//stl_construct.h部分
template <class _T1, class _T2>
inline void _Construct(_T1* __p, const _T2& __value) 
  new ((void*) __p) _T1(__value);//指定调用_T1的构造函数_T1::_T1(__value)来初始化指针__p所指向的内存 (placement new)


template <class _T1>
inline void _Construct(_T1* __p) 
  new ((void*) __p) _T1();//与上述类似,默认构造函数

在本条款中,给出的定义:
如果operator new接受的参数除了一定会有的那个size_t之外还有其他,这个是所谓的placemet new。
比如说:

void *operator new(std::size_t ,void *pmem)throw();
void *operator new(std::size_t ,ostream&logstream)throw();

这个可能是更为广义的定义。

Widget *pw = new(cerr) Widget;

如果我们想在构造的Widget时传入ostream实参,那么可以按上述写法,同样的,如果构造失败,我们必须释放内存,此时不会调用标准的版本,需要我们自己写一个operator delete,这就是本条款的核心:

void opertor delete(void *,ostream&)throw;

这个规则很简单,就是当我们使用了带有额外参数的operator new我们也要写个带同样参数的operator delete.

在继承体系中,class的 placement new往往会遭遇遮掩的问题,作者给了一解决方案:

class StandardNewDeleteForm

public:
    //标准 new/delete
    //placement new/delete
    //nothrow new/delete
;

class Widget :public StandardNewDeleteForm

public:
    using StandardNewDeleteForm::operator new;
    using StandardNewDeleteForm::operator delete;
    //添加自定义的placement new/delete
;

以上是关于Effective C++笔记(11)—定制new和delete的主要内容,如果未能解决你的问题,请参考以下文章

《Effective C++》读书笔记汇总

《Effective C++》学习笔记

《More Effective C++》总结笔记——异常

《More Effective C++》总结笔记

effective c++学习笔记

Effective C++ 笔记:4设计与声明