Effective C++ 笔记 第八部分 定制new和delete

49.了解new-handler的行为(Understand the behavior of the new-handler)


set_new_handler允许客户指定一个函数,在内存分配无法获得满足时被调用。
Nothrow new是一个颇为局限的工具,因为它只适用于内存分配;后继的构造函数调用还是可能抛出异常。

当operator new无法满足某一内存分配需求时,他会先调用一个客户指定的错误处理函数,一个所谓的new-handler。该函数无参数,返回void。

void outOfMem(){
    std::cerr<<"Unable to satisfy request for memory\n";
    std::abort();//另程序中止,不然会一直调用outOfMme()
}

int main(int argc, const char * argv[]) {
    std::set_new_handler(outOfMem);
    int* p = new int[9999999999999999999L];
    return 0;
}
//输出Unable to satisfy request for memory

一个好的new-handler应做以下事情:
1.让更多内存可以被使用。
2.安装另外一个new-handler。当期new-handler无法取得更多内存时,若知道哪个new-handler可以获得更多内存,那么就设置其为new-handler。
3.卸除new-handler。将null传给set_new_handler以卸除。这样new会在内存分配不成功时抛出异常。
4.抛出bad_alloc的异常。这样的异常不会被operator new捕捉,因此会传播到内存索求处。
5.不返回,通常调用abort或exit。

有时候我们希望把new_handler与class绑定,在不同class的operator new无法满足内存分配需求时,调用不同的new_handler.我们将此功能封装于NewHandlerSupport;


//使用RAII操作
//NewHandlerSupport中并不使用typename T
//但此处使用template的目的是位不同的类独立生成一个currentHandler成员变量
template<typename 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);
    NewHandlerSupport() = default;

    explicit NewHandlerSupport(std::new_handler nh)
    { handler = nh; }//记录旧的new_handler;

    ~NewHandlerSupport(){
        //离开作用域时将new_handler设置回去
        std::set_new_handler(handler);
    }
private:
    static std::new_handler currentHandler;//用来记录将要设置的new_handler
    static std::new_handler handler;//用来记录旧的new_handler

    NewHandlerSupport(const NewHandlerSupport&);//阻止copying
    NewHandlerSupport& operator=(const NewHandlerSupport*);
};

template<typename T>
std::new_handler NewHandlerSupport<T>::set_new_handler(std::new_handler p){
    std::new_handler oldHandler = currentHandler;//保存旧new_handler
    currentHandler = p;//设置新new_handler
    return oldHandler;//返回旧new_handler
}

template<typename T>
void* NewHandlerSupport<T>::operator new(std::size_t size)
throw(std::bad_alloc)
{
    //设置currentHandler为new_handler
    //并将返回值——旧的new_handler通过构造函数保存至handler
    NewHandlerSupport h(std::set_new_handler(currentHandler));
    return ::operator new(size);
    //将调用h的析构函数,用以将new_handler设置回去
}

template<typename T>
std::new_handler NewHandlerSupport<T>::currentHandler = 0;
template<typename T>
std::new_handler NewHandlerSupport<T>::handler = 0;

class Widget: public NewHandlerSupport<Widget>{
public:
    Widget()
    {
    }
    ~Widget();
    int a[99999999999999999L];//为了分配失败
};

void outOfMem(){
    std::cerr<<"Unable to satisfy request for memory\n";
    std::abort();
}

int main(int argc, const char * argv[]) {
    Widget::set_new_handler(outOfMem);
    Widget* pw = new Widget();//如果内存分配失败则调用outOfMem
    //之后若分配内存失败将调用其他new_handler
    return 0;
}

50.了解new和delete的合理替换时机(Understand when it makes sense to replace new and delete.)


有许多理由需要写个自定的new和delete,包括改善性能,对heap运用错误进行调试,收集heap使用信息;

重载new和delete的理由:
1.用来检测运用上的错误。
例如我们可以自定义new,用来超额分配内存,早客户所得的内存区块前后放置特定的byte签名,若由于一些编程错误导致overruns(写入点在分配器块尾端之后)或underruns(在起点之前),我们可以在自定义的delete种检测并志记。
2.为了收集动态分配内存之使用统计信息。
3.为了增加分配和归还的速度。
4.为了降低缺省内存管理器带来的空间额外开销。
5.为了弥补缺省分配器中的非最佳齐位。
6.为了将相关对象成蔟集中。降低缺页率。
7.为了获得非传统行为。

51.编写new和delete时需固守常规(Adhere to convention when writing new and delete.)


operator new 应该内含一个无穷循环,并在其中尝试分配内存,如果它无法满足内存需求,就该调用new-handler.它也应该有能力处理0bytes申请。class专属版本则还应该处理”比正确大小更大(错误)的申请”。
operator delete应该在收到null指针时不做任何事。class专属版本则还应该处理”比正确大小更大(错误)的申请”。

operator new中处理0byte申请的方法是视为1byte申请。c++要求客户要求0byte,operator new也要返回一个合法指针

if(size == 0){
    size = 1;
}

operator new还需正确处理比正确大小更大的申请,这种情况发生的场合是derived class继承了带有operator new的base class,但derived class并没有声明operator new,所以new derived();调用的是base class的operator new。解决方法是遇到这种情况则调用标准的operator new;

if(size != sizeof(Base)){
    return ::operator new(size);
}

此种做法包含了处理0byte申请,因为sizeof(Base)不会返回0.原因
我们还需要实现new_handler,在operator new中提供一个无限循环,除非分配成功return,要么调用new_handler;

while(true){
    尝试分配size bytes;
    if(分配成功)
    returnif(new_handler != NULL)
    调用new_handler;
    else
    throw std::bad_alloc();

}

实现operator new[]只需要分配一块未加工过的内存,因为无法对array之内迄今尚未存在的对象做任何事。甚至不知道每个对象的大小,因为这其中涉及了继承。

operator delete需要保证删除null指针安全。

if(rawMemory == 0) 
return;

operator delete需要正确处理比正确大小更大(错误)的申请,原理同new;

if(size != sizeof(Base)){
    ::operator delete(rawMemory);
    return;
}

52.写了placement new也要写placement delete(Write placement delete if you write placement new.)


当你写一个placement operator new,请确定也写出了对应的placement operator delete.如果没有这么做,你的程序可能会发生隐微而时断时续的内存泄露。
当你声明placement new和placement delete,请确定不要无意识(非故意)地掩盖了他们的正常版本。

当写出一个表达式如下:

Widget* pw = new Widget();

其中有两个函数被调用,一是分配内存的operator new,二是 widget的default构造函数。当第一个函数成功,但第二个函数抛出异常。那么C++将寻找与new对应的delete来释放内存。
但假如写了一个class专属的operator new,其接受除了那个一定会有是size_t参数还有其他参数,这样的operator new被成为placement new。
在placement情况下,若new成功但构造失败,那么编译器将会寻找与placement new有相同参数的placement delete。若无声明对应的placement delete,那么将会什么也不做,导致内存泄露。

你可能感兴趣的:(C++)