Effective C++ Item 49: Customize new and delete-understand the behavior of new-handler

One, set_new_handler() function

  • Set_new_handler() function syntax introduction, please refer to: https://blog.csdn.net/qq_41453285/article/details/103553037
  • When opertor new allocates memory:
    • In the old C++ standard, let operator new return null
    • In the modern C++ standard, operator new throws an exception (bad_alloc), and the exception cannot be caught by operator new, so it will be propagated to the memory request
  • We can use set_new_handler() to bind an error handling function before using operator new . This error handling function will be called and executed when operator new allocates memory when there is insufficient memory.

Definition of set_new_handler() function

  • This function is defined in the <new> header file
  • The source code is as follows, which is actually a typedef

  • The function accepts a new_handler parameter (this parameter usually points to a function), and returns a new_handler data
  • throw() specifier: Indicates that the function will not throw an exception. It has been changed to noexcept in C++11

Use demo case

  • The following is a function we defined, which is called when operator new cannot allocate enough memory:
void outOfMem()

{

    std::cerr << "Unable to satisfy request for memory\n";

    std::abort();

}


int main()

{

    //绑定函数

    std::set_new_handler(outOfMem);


    //如果此处内存分配失败,将会调用outOfMem()

    int *pBigDataArray = new int[100000000L];


    return 0;

}

 

Two, the design principle of new_handler function

  • When the memory allocation is insufficient, the new_handler function specified by the set_new_handler() parameter will be called
  • If the new_handler function is executed when the memory is insufficient, but we also perform dynamic memory allocation in the new_handler function at the same time, the consequences are unpredictable
  • Therefore, a well-designed new_handler function has the following principles:
    • ① Make more memory available : In this approach, a large block of memory is allocated at the beginning of the program execution, and then when the new_handler function is executed for the first time, they are released to the system
    • ② Install another new_handler :
      • If the current new_handler cannot obtain more available memory, perhaps it knows which other new_handler has this ability
      • At present, this new_handler can install another new_handler to replace itself (as long as set_new_handler is called)
      • Next time when operator new calls new_handler, it will call the newly installed one (one of the variations of this melody is to let new_handler modify its behavior, so when it is called next time, it will do something different. In order to achieve For this purpose, one of the methods is to make new_handler modify the static data, namespace data or global data that "will affect the behavior of new_handler")
    • ③Remove new_handler : that is, pass the null pointer to set_new_handler. Once no new_handler function is installed, operator new will throw an exception when the memory allocation is unsuccessful
    • ④ Throw bad_alloc (or derived from bad_alloc) exception : such exception will not be caught by operator new, so it will be propagated to the memory request
    • ⑤No return : usually call abort() or exit

Three, set the new-handler behavior for the class

  • Sometimes we want to set the new-handler behavior separately for each class
  • For example, if the allocation operation of different classes fails, the corresponding new-handler function is called. For example, the following is pseudo code:
class X {

public:

    static void outOfMemory();

};


class Y {

public:

    static void outOfMemory();

};


int main()

{

    X* p1 = new X; //如果new失败了,我们希望调用X::outOfMemory()

    Y* p2 = new Y; //如果new失败了,我们希望调用Y::outOfMemory()

    return 0;

}

Implement the new-handler behavior of the class

  • By default, C++ does not support class-specific new-handlers behavior, but we can implement it ourselves
  • The implementation method is:
    • Set operator new() memory allocation function for class, and set_new_handler() function
    • Then add a new_handler member function, which represents the call function of this class when new fails
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;

};
  • Initialization of member functions and member variables:
    • currentHandler represents the memory allocation error handling function of the current class, which is set to 0 during initialization
    • set_new_handler() is used to set the current new_handler function, the principle is similar to the standard library set_new_handler() function
std::new_handler Widget::currentHandler = 0;


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

{

    std::new_handler oldHandler = currentHandler;

    currentHandler = p;

    return oldHandler;

}
  • For Widget::operator new(), we need to define another class, which is encapsulated by RAII, which obtains a resource during the construction process and releases it during the destruction process. code show as below:
class NewHandlerHolder

{

public:

    explicit NewHandlerHolder(std::new_handler nh)

    :handler(nh) {}

    ~NewHandlerHolder() {

        std::set_new_handler(handler);

    }

private:

    std::new_handler handler;

    //阻止拷贝操作

    NewHandlerHolder(const NewHandlerHolder&);

    NewHandlerHolder& operator=(const NewHandlerHolder&);

};
  • The implementation of Widget::operator new() is as follows:
void* Widget::operator new(std::size_t size)throw(std::bad_alloc)

{

    //我们调用std::set_new_handler(),使其返回全局的new-handler

    //然后使用全局的new-handler初始化h
    
    NewHandlerHolder h(std::set_new_handler(currentHandler));


    //分配实际内存操作

    return ::operator new(size);


}

//不论分配是否成功,函数结束之后,h自动析构

//其析构函数中调用std::set_new_handler(),将之前的全局new-handler设置回来
  • The client should use the Widget class in the following format:
void outOfMem();


int main()

{

    //设定outOfMem()为Widget的new-handler函数

    Widget::set_new_handler(outOfMem);


    //如果内存分配失败,调用outOfMem()

    Widget* pw1 = new Widget;


    //Widget::operator new()执行结束后还会将全局new-handler设置回来


    //如果内存分配失败,调用全局new-handler函数

    std::string* ps = new std::string;


    //设定Widget的new-handler函数为null

    Widget::set_new_handler(0);


    //如果内存分配失败,立刻抛出异常(因为Widget的new-handler函数置空了)

    Widget* pw2 = new Widget;


    return 0;

}

 

Four, encapsulate a base class of new-handler behavior

  • In "Three", the Widget we finally designed has its own new-handler function. With the help of the primitive implementation of Widget, we want to design a class that only has the new-handler processing behavior, and then let other classes inherit from this class, then each class can have its own new-handler behavior
  • to this end:
    • We re-encapsulate the function in Widget into a separate class and implement new-handler behavior in it
    • In order to make each class have its own new-handler behavior, we set the class as a template, and then let the respective classes inherit from this template
    • When the class is defined, the template will be instantiated, so different classes will have their own new-handler behavior
  • code show as below:
//定义为模板

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);

private:

    static std::new_handler currentHandler;

};


//仍未非模板

class NewHandlerHolder

{

    //内容同上

};
  • With this template, we can inherit our class from this class, and then our class will also have its own new-handler behavior. E.g:
//继承NewHandlerSupport

class Widget :public NewHandlerSupport<Widget>{};


class Widget2 :public NewHandlerSupport<Widget2>{};
  • When our different classes inherit from NewHandlerSupport, when defining objects, we will instantiate different NewHandlerSupport templates respectively, which will not interfere with each other.
  • to sum up:
    • The above NewHandlerSupport template, we usually call it a "mixin" style base class
    • Inheriting from NewHandlerSupport may lead to disputes caused by multiple inheritance (see Clause 40)

Five, nothrow description

  • When opertor new allocates memory:
    • In the old C++ standard, let operator new return null
    • In the modern C++ standard, operator new throws an exception (bad_alloc), and the exception cannot be caught by operator new, so it will be propagated to the memory request
  • Nothrow specifier: In order to maintain compatibility with the previous C++ standard that returns null when the new operator fails to allocate, C++ provides this keyword. Using this keyword allows operator new() to return null when memory allocation fails. Throw bad_alloc exception
  • E.g:
class Widget {};


int main()

{

    //当new出错时,抛出bad_alloc异常

    Widget* pw1 = new Widget;


    //当new出错时,返回null

    Widget* pw2 = new (std::nothrow) Widget;
    
    return 0;

}
  • Precautions:
    • nothrow is only related to operator new, let it return null when it fails to apply for memory
    • But it has nothing to do with the constructor of the type behind it. For example, whether the constructor of the Widget above will throw an exception has nothing to do with nothrow

Six, summary

  • set_new_handler allows customers to specify a function to be called when the memory allocation cannot be satisfied
  • Nothrow new is a rather limited tool because it only applies to memory allocation; subsequent constructor calls may still throw exceptions

Guess you like

Origin blog.csdn.net/www_dong/article/details/113848862