effective C++ 学习 (Customizing new and delete)

来源:互联网 发布:网络拥塞控制方法 编辑:程序博客网 时间:2024/06/05 22:50

effective C++ 学习 (Customizing new and delete)

Item 49: Understand the behavior of the new-handler.

1.     Heap that containers in STL useis directly managed by allocator object but not new and delete.

2.     “operator new” will call the functionthat appointed by users before it throws exception. Users can appoint theprocessing functions by set_new_handler, as following:

namespace std

{

  typedef void (*new_handler)();

  new_handler set_new_handler(new_handler p) throw();

}

       Note:the parameter of this function is user-defined handler, and the return value isalso a function pointer, which points the handler function that was used beforecalling the function set_new_handler.

3.     A good new-handler functionshould do the following things well:

1)     Utilize more memory as soon aspossible;

2)     Install the other new-handler;

3)     Uninstall new-handler by nullpointer ;

4)     Throw bad_alloc, this exceptioncan’t be captured by “operator new”;

5)     No return to be more flexible.It is more convenient for you to implement new-handler.

4.     Defining exclusive(专属的) new-handlers for user-defined classes.

#include<iostream>

 

class NewHandlerHolder

{

public:

   explicitNewHandlerHolder(std::new_handler nh):handler(nh){}

   ~NewHandlerHolder(){std::set_new_handler(handler);}// recovery old new-handler

private:

   std::new_handler handler;// save old new-handler

   NewHandlerHolder(constNewHandlerHolder&);

   NewHandlerHolder& operator=(constNewHandlerHolder&);

};

 

class Widget

{

public:

   staticstd::new_handler set_new_handler(std::new_handler p)throw();

   static void* operator new[](size_t size);

private:

   staticstd::new_handler currentHandler;

};

//static members must be defined outside of class

//except static const int member

std::new_handlerWidget::currentHandler = 0;

 

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

{

   std::new_handler oldHandler = currentHandler;

   currentHandler = p;

   returnoldHandler;

}

 

void* Widget::operatornew[](std::size_t size) throw(std::bad_alloc)

{

   NewHandlerHolder                       //install new new-handlerof Widget

       h(std::set_new_handler(currentHandler));//and save old new-handler

   return ::operatornew[](size);            //allocatememory

}                                         //recover old global new-handler

void outOfMem()

{

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

   std::abort();

}

int main()

{

   Widget::set_new_handler(outOfMem);

   Widget* pw1 = newWidget[2000000000];

   Widget::set_new_handler(0);

   Widget* pw2 = newWidget[1000000000];

   return 0;

}

Note: these are the following problems neededto pay attention:

1)     The old new-handler functionpointer should be regarded as resource to be managed by RAII (Resource Acquisition Is Initialization).It is the NewHandlerHolder class that is used to manage old new-handlerfunction pointer;

2)     The place of “return ::operatornew[](size); ” will throw exception and the function of outOfMem() will becalled. After that, the destructor of NewHandlerHolder will be called torecover new-handler function of system.

3)     All objects of a class sharethe same static member variables. If the static member variables belong to baseclass, all objects of derived classes will shared the same static membervariables.

5.     The operators above is similarto all user-defined classes, so the choice of designing a base class with styleof “mixin” will be a good choice. We had better to implement it by template.Before we start to write, let’s analysis the reasons:

#include<iostream>

//template<typename T>

class Base

{

public:

   virtual~Base(){}

   virtual void outBaseMemberPointer(){std::cout<<&this->m_base<<std::endl;}

private:

   static int m_base;

};

//template<typename T>

int Base/*<T>*/::m_base= 0;

class Derived1: public Base/*<Derived1>*/

{

};

class Derived2: public Base/*<Derived2>*/

{

};

int main()

{

   Derived1 test1;

   Derived2 test2;

   test1.outBaseMemberPointer();

   test2.outBaseMemberPointer();

}

Result:


Note: As shown in the picture, the derivedobjects of test1 and test2 share the same static member variable of m_base dueto the same address. Obviously, if we use normal base class to encapsulate (封装) “operatornew” and “static std::new_handler set_new_handler(std::new_handler p)”, its derived classes will share the same “m_base” static membervariables. In order to achieve each derived class have own static membervariables, we should use template.

#include<iostream>

template<typename T>

class Base

{

public:

    virtual ~Base(){}

    virtual voidoutBaseMemberPointer(){std::cout<<&this->m_base<<std::endl;}

private:

    static int m_base;

};

template<typename T>

intBase<T>::m_base = 0;

class Derived1: public Base<Derived1>

{

};

class Derived2: public Base<Derived2>

{

};

int main()

{

    Derived1test1;

    Derived2test2;

    test1.outBaseMemberPointer();

    test2.outBaseMemberPointer();

}


Note: we discover the derived objects havedifferent static member variables.

6.     The base class with the styleof “mixin”:

#include<iostream>

 

class NewHandlerHolder

{

public:

   explicitNewHandlerHolder(std::new_handler nh):handler(nh){}

   ~NewHandlerHolder(){std::set_new_handler(handler);}// recovery old new-handler

private:

   std::new_handler handler;// save old new-handler

   NewHandlerHolder(constNewHandlerHolder&);

   NewHandlerHolder& operator=(constNewHandlerHolder&);

};

 

template<typename T>

class NewHandlerSupport

{

public:

   staticstd::new_handler set_new_handler(std::new_handler p)throw();

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

private:

   staticstd::new_handler currentHandler;

};

// static member variables must be defined outside of class

template<typename T>

std::new_handlerNewHandlerSupport<T>::currentHandler = 0;

 

template<typename T>

std::new_handlerNewHandlerSupport<T>::set_new_handler(std::new_handler p)throw()

{

   std::new_handler oldHandler = currentHandler;

   currentHandler = p;

   returnoldHandler;

}

 

template<typename T>

void* NewHandlerSupport<T>::operatornew(size_t size)throw(std::bad_alloc)

{

   NewHandlerHolderh(std::set_new_handler(currentHandler));

   return ::operatornew(size);

}

 

class Widget: publicNewHandlerSupport<Widget>

{

};

 

void outOfMem()

{

   std::cerr<<"outof memory.\n";

   std::abort();

}

int main()

{

   Widget::set_new_handler(outOfMem);

   Widget* w = newWidget;

}

Note: these are some the followingproblems:

1)     Different derived classes havedifferent entity (实体) due todifferent template parameters;

2)     Use the derived classes toinstantiate base classes, which is named as curiously recurring templatepattern(CRTP 怪异的循环模板模式);

3)     Use “std::nothrow” not to ensurethat no exceptions are thrown;

4)     The size of new operator issize of member variables after aligned.

Item 50: Understand when it makes sense to replace new anddelete.

1.     The reasons of replacingsystem-defined “operator new” and “operator delete” with user-defined “operatornew” and “operator delete”:

1)     Detecting runtime errors; sometimesthe mistakes of overruns(写入点在分配区块尾端之后) andunderruns(写入点在分配区块起点之前) would happen, howeverfor user-defined operator new can achieve larger space to place byte patterns(signatures) to detect these mistakes.

2)     In order to enforce function;custom memory allocation policy for special application situation.

3)     Collect using data. Know thesize of allocation memory, memory life and order of memory allocation ofrelease (e.g.LIFO, FIFO, random in and out). It is easy for user-definedoperator new to collect;

4)     Increase speed of memoryallocation and release; first of all you need to know which function your programbottlenecks lie;

5)     In order to decrease the extra expensefor default memory manager;

6)     In order to make up thesuboptimal alignment of default memory manager;

7)     In order to gather relativedata to less pages;

8)     In order to do something thatdefault operator new and operator delete don’t do, like manager shared memoryby C API, define operator delete to release memory covered by 0 to strengthendata safety.

2.     Defining own operator new andoperator delete, you must consider some details like alignment, system portabilityand thread safety etc.

Item 51: Adhere to convention when writing new and delete.

1.     Non-member operator newpseudocode(伪码):

void* operatornew(std::size_t size) throw(std::bad_alloc)

{

   using namespace std;

   if(size == 0)

   {

       size = 1;

   }

   while(true)

   {

       //try toallocate size bytes

       if(Assignmentsuccess)

          return(a pointer that points memory aligned);

       //allocate fail,find current new-handling function

       new_handler globalHandler =set_new_handler(0);

       set_new_handler(globalHandler);

       if(globalHandler)((*globalHandler)());

       else throw std::bad_alloc();

   }

}

Note: in order to make the program breakfrom “while(true)”, the following things need to be done:

1)     Allocate memory successfully;

2)     New-handling must do thefollowing things:

(1.   Make more memory be used;

(2.   Install another new-handling;

(3.   Uninstall new-handler;

(4.   Throw bad_alloc exception.

2.     When the “operator new” of base class is derived, it will bring errors that it is calledby derived classes due to difference between base class and derived class.

void* Base::operatornew(std::size_t size) throw(std::bad_alloc)

{

   if(size != sizeof(Base))

       return ::operatornew(size);

}

Note: if the size errors happen, we callthe standard “operatornew”. The following problemsneed to be noticed:

1)     The result of sizeof(Base) ismore than 0;

2)     If the size is 0, the standard“operatornew” will be able to dealwith this question well.

3.     If you need to implement “operator new[]”, what you should only do is to allocate some raw memory (未加工的内存), because you cannot know the size of an object and the number ofobjects.

4.     If you design a delete, youneed to deal with the situation when the pointer is 0, as following:

void operatordelete(void *rawMemory)throw()

{

   if(rawMemory== 0) return;

   now, returnmemory that rawMemory points

}

5.     If “operator new” hand in the allocationoperation with error size, your “operatordelete” should do too, asfollowing:

void Base::operatordelete(void*rawMemory, std::size_t size)throw()

{

   if(rawMemory== 0) return;

   if(size != sizeof(Base))

   {

       ::operatordelete(rawMemory);

       return;

   }

   now, returnmemory that rawMemory points

   return;

}

Note: if there’s no virtual destructor inBase class, C++ may transmit a wrong size, which is one of reasons why weshould use virtual destructor in base class.

Item 52: Write placement delete if you write placementnew.

1.     “placement new” and “placementdelete” represents the versions of “operator new” and “operator delete” withextra parameters. For example:

void*operatornew(std::size_t,void* pMemory)throw();

Note: there are a lot of times, the placement new represents thefunction above, but in general it denotes operator new with extra parameters. Thedifference of this version of placement new is that it can assign memory on thespecified location.

2.     Write placement delete if youwrite placement new. When we assign memory by using “operatornew”, there are two operatorsrunning. One is to call “operatornew” to assign memory, anotherone is to call the constructor of the class. If the constructor throwsexceptions, system need to recover state before “operatornew” was called, which isdone by system during running time because users don’t have the pointers thatpoint to the memory. If you assign memory by placement new, system must useplacement delete corresponding to placement new to release the memory.Therefore you must write placement delete if you have written placement new.

class Widget

{

public:

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

};

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

3.     If you write placement new, youneed to write placement delete, which is all right, but if you assign memorysuccessfully and you need to release it afterwards, so you also need a normaloperator delete, like this:

class Widget

{

public:

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

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

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

};

4.     Avoid covering the standardversion unconsciously.

“operator new” in the global scope.

void* operatornew(std::size_t)throw(std::bad_alloc);

void* operatornew(std::size_t,void*)throw();

void* operatornew(std::size_t,const std::nothrow_t&)throw();

If you don’t want to cover the “operator new” in the globalscope, you can implement it by the following base class and declare them byusing in the derived classes.

classStandardNewDeleteForms

{

public:

    //normal new/delete

    static void* operator new(std::size_tsize)throw(std::bad_alloc)

    {      return ::operatornew(size);    }

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

    {   ::operator delete(pMemory);}

    //placement new/delete

    static void* operator new(std::size_tsize,void *ptr) throw()

    {   return ::operatornew(size,ptr);}

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

    {   return ::operatordelete(pMemory,ptr);}

    // nothrow new/delete

    static void* operator new(std::size_tsize,const std::nothrow_t& nt) throw()

    {   return ::operatornew(size,nt);}

    static void operator delete(void * pMemory,conststd::nothrow_t& nt) throw()

    {   return ::operatordelete(pMemory);}

};

 

class Widget:public StandardNewDeleteForms

{

public:

    // get standard forms by using

    using StandardNewDeleteForms::operatordelete;

    using StandardNewDeleteForms::operatornew;

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

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

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

};

int main()

{

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

   

    return 0;

}


阅读全文
0 0
原创粉丝点击