Effective C++ Item 52: Customize new and delete—write placement delete if you write placement new

1. Normal signature new and delete

  • When using new to create an object, it may throw an exception in two places:
    • ① When calling new(), an exception may be thrown in the new() function
    • ②If the new() function does not throw an exception, but the constructor of the following object may throw an exception
  • When the new() function does not throw an exception, and the object's constructor throws an exception, the system has already applied for memory for the object (new() application), so if the constructor throws an exception, then the system needs Release the memory allocated as shown in the new() operation . in case:
    • If the program uses the normal signature new/delete, then when new() does not throw an exception, and the object's constructor throws an exception, delete() will automatically undo everything new() has done to restore it Intact (release memory, etc.)
    • If the program uses abnormal signature new/delete, then the consequences will be
  • The normal signature type new and delete represent the default types of new and delete, and they are usually paired . E.g:
//正常签名式的new(全局式的)

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

//正常签名式的delete(全局式的)

void operator delete(void* rawMemory)throw();


//正常签名式的delete(成员函数版的)

void operator delete(void* rawMemory,std::size_t size)throw();

Demonstration description

  • Next we create a Widget object:
//正常签名式的new(全局式的)

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

//正常签名式的delete(全局式的)

void operator delete(void* rawMemory)throw();


class Widget {};


Widget* pw = new Widget; //使用正常签名式的new创建对象
  • The above Widget uses the normal signature style new to create an object, if:
    • If the new() function throws an exception, the program terminates (the program does not have any memory at this time, and it will not cause a memory leak)
    • If the new() function does not throw an exception, and the Widget constructor throws an exception, then new() has already applied for memory, so we need to release this memory, otherwise it will cause a memory leak
  • Because if the constructor throws an exception and the pw pointer has not been assigned, the client cannot obtain the pointer to return the memory, so the memory return operation here is handed over to the C++ runtime system. The rules are:
    • If the object is created using normal signature new, the system automatically calls normal signature delete to release the memory requested by new
    • Therefore, our program will automatically call delete to release memory if new does not throw an exception but the constructor throws an exception.

二、placement new

  • What is placement new: For the general new function, it has only one parameter (size_t), but if we add one more parameter to the new() function, then we call this new placement new
  • For example, the following is a Widget, which has:
    • A placement new ( abnormal form) whose parameter 2 is used to record related allocation information
    • A normal form of new is the normal signature type introduced in "One"
class Widget {

public:

    //placement new(因为其带有一个ostream的参数)

    static void* operator new(std::size_t size,std::ostream& logStream)

    throw(std::bad_alloc);


    //正常签名式的delete

    static void operator delete(void *rawMemory, std::size_t size)

    throw();

};
  • A special placement new:
    • For details about this placement new, please refer to: https://blog.csdn.net/qq_41453285/article/details/103547699
    • In C++, when we mention placement new (positioning new), we generally refer to this special placement new, which is also a type of placement new.
    • The parameter 2 is a pointer used to allocate memory on the specified memory. The code is as follows:
//placement new

void* operator new(std::size_t size,void* pMemory)throw();

placement new may cause memory leaks

  • For example, the Widget we defined above is as follows:
class Widget {

public:

    //非正常签名式的new(因为其带有一个ostream的参数)

    static void* operator new(std::size_t size,std::ostream& logStream)

    throw(std::bad_alloc);


    //正常签名式的delete

    static void operator delete(void *rawMemory, std::size_t size)
    
    throw();

};
  • When we use the following code to define a Widget, it may cause a memory leak. The reasons are as follows:
    •  If placement new does not throw an exception, then nothing will happen
    • If placement new does not throw an exception, and when the Widget constructor throws an exception, the pw pointer and the pw pointer have not been assigned, and the client cannot obtain the pointer to return the memory (the situation at this time is in the "1" Same as the demo case). But at this time we are using the object created by placement new, so the C++ system goes back to find whether there is placement delete to restore the original state, but Widget is not defined, so the runtime system does not know how to cancel and restore the original call to placement new
//调用placement new版本创建Widget对象

//如果placement new未抛出异常,而Wiget构造函数抛出异常,那么此段代码会内存泄漏

Widget* pw = new (std::cerr) Widget;

 

To solve the error of placement new, define a placement delete for it

  • Because of our definition of placement new above, when new does not make an error and the constructor fails, the runtime system cannot find a corresponding placement delete to cancel and restore the original call to placement new.
  • Therefore, for custom placement new, we also need to customize a placement delete. The code is as follows:
class Widget {

public:

    //placement new

    static void* operator new(std::size_t size, std::ostream& logStream)

    throw(std::bad_alloc);


    //placement delete(与上面的new是配对的)

    static void operator delete(void* pMemory, std::ostream& logStream)throw();


    //其他代码同上

};
  • At this time, when we call placement new to create an object, new is an error and the class constructor fails , it calls placement delete to cancel and restore the original call to placement new

For the call to placement delete

  • When we define placement new, we usually also define a placement delete. Therefore, there are two types of delete in the class at this time (one is the normal signature type, and the other is placement delete)
  • So when we use delete to destroy an object, we call the normal signature delete instead of placement delete . E.g:
class Widget {

public:

    //placement new

    static void* operator new(std::size_t size, std::ostream& logStream)
    
throw(std::bad_alloc);


    //placement delete(与上面的new是配对的)

    static void operator delete(void* pMemory, std::ostream& logStream)throw();


    //正常签名式的delete

    static void operator delete(void* pMemory)throw();


    //其他代码同上

};



//调用placement new创建对象

Widget* pw = new (std::cerr) Widget;


//调用正常签名式的delete删除对象

delete pw;

 

Third, the hiding of standard new by placement new

  • By default, C++ provides the following form of operator new in the global scope:

Demo case

  • When our class defines placement new, but does not define the normal signature new for this class, then for this class, it can only use placement new to create objects, not the normal signature new, because the global is normal The signature new is hidden
  • E.g:
class Base {

public:

    //只定义了placement new

    static void* operator new(std::size_t size, std::ostream& logStream)

    throw(std::bad_alloc);


    //全局的正常签名式的new将被隐藏

};


class Derived :public Base {

public:

    //其继承了Base的placement new


    //自己还定义了正常签名式的new

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

};


int main()

{

    Base* pb = new Base; //错误,全局的正常签名式的new被隐藏了

    Base* pb2 = new(std::cerr) Base; //正常


    Derived* pd=new (std::cerr) Derived; //正常,使用Base的placement new创建对象

    Derived* pd2 = new Derived; //正常,其自己定义了正常签名式的new


    return 0;
    
}

 

Four, encapsulate a Base class

  • According to all the summaries of the previous article, now we encapsulate a Base class to include all normal forms of new and delete
  • Of course, after the C++ standard is updated, the global new and delete will be changed, so this class also needs to be updated
  • code show as below:
class StandardNewDeleteForms{

public:

    //正常签名式的new和delete

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

        return ::operator new(size);

    }

    static void operator delete(void* pMemory)throw() {

        ::operator delete(pMemory);

    }


    //placement new和placement delete

    static void* operator new(std::size_t size,void* ptr)throw(std::bad_alloc){

        return ::operator new(size,ptr);

    }

    static void operator delete(void* pMemory, void* ptr)throw() {

        ::operator delete(pMemory, ptr);

    }


    //nothrow new/nothrow delete

    static void* operator new(std::size_t size, const std::nothrow_t& nt)throw(){
    
        return ::operator new(size,nt);

    }

    static void operator delete(void* pMemory, const std::nothrow_t& nt)throw() {

        ::operator delete(pMemory);

    }

};
  • The client's own class can inherit from this class and get all its new and delete . If you define new and delete yourself, in order to prevent hiding new and delete in StandardNewDeleteForms, you can use the using statement. code show as below:
class Widget :public StandardNewDeleteForms {

public:

    //使基类中所有new和delete在派生类中可见

    using StandardNewDeleteForms::operator new;

    using StandardNewDeleteForms::operator delete;


    //再定义自己的new与delete

    static void* operator new(std::size_t size, std::ostream& logStream)throw(std::bad_alloc);

    static void operator delete(void* pMemory, std::ostream& logStream)throw();

};

Five, class's use of operator new summary

  • For new:
    • ① When the class defines the normal signature type new, the global normal signature type new and placement new will be hidden, and its creation objects can only use their own new creation objects
    • ② Same as above, when the class defines placement new, the global normal signature new and placement new will be hidden, and the object created can only use its own placement new to create objects
    • ③ After your class creates placement new, in order to prevent the object from being created, its constructor throws an exception and cannot be processed, it is recommended to define a placement delete at the same time, and the constructor throws an exception when the object is created by placement new. Restoration treatment

Six, summary

  • When you write a placement operator new, make sure you also write the placement operator delete of the object. If this is not the case, your program may experience subtle and intermittent memory leaks
  • When you declare placement new and placement delete, make sure not to unconsciously (unintentionally) cover up their normal versions

Guess you like

Origin blog.csdn.net/www_dong/article/details/113857100
Recommended