天天看点

Effective C++笔记 —— 第八章

  1. 当operator new抛出异常以反映一个未满足的内存需求之前,它会先调用一个客户指定的错误处理函数,一个所谓的set-new-handler
    namespace std{
     typedef void(*new_handler)();
     new_handler set_new_handler(new_handler p) throw();
    }
               

    new_handler是个typedef,定义出一个指针指向函数,该函数没有参数也没有返回值。set_new_handler则是“获得一个new_handler并返回一个new_handler”的函数。

    可以这样用set_new_handler:

    void outOfMem(){
    	std::cerr<<"unable to satisfy request for memory";
    	std::abort();
    }
    int main()
    {
    	std::set_new_handler(outOfMem);
    	int* pBigDataArray=new int[1000000000000];
    }
               
    如果想支持class专属之new_handler,可以令每一个class提供自己的set_new_handler和operator new即可,其中set_new_handler使客户得以指定class的专属new_handler,至于operator new则确保在分配class对象内存的过程中以class专属之new_handler替换global new_handler
  2. 齐位意义重大,因为C++要求所有operator new返回的指针都有适当的对齐。malloc就是在这样的要求下工作,所以令operator new返回一个得自malloc的指针是安全的。如果返回一个得自malloc且偏移一个int大小的指针,没人能保证它的安全
  3. void* operator new(std::size_t size) throw(std::bad_alloc)
     	{
     		using namespace std;
     		if(size==0)
     			size=1;
     		while(true)
     		{
     			尝试分配size bytes
     			if(分配成功)
     			return (一个指针,指向分配得来的内存);
     			//分配失败;找出目前的new-handling函数
     			new_handler globalHandler=set_new_handler(0);
     			set_new_handler(globalHandler);
     			
     			if(globalHandler)  (*globalHandler)();
     			else throw std::bad_alloc();
     		}
     	}
               

    我们没有办法直接取得new_handler函数指针,所以必须调用set_new_handler找出它来

    while是个无穷循环,退出循环的唯一办法是:内存被分配成功或new_handlering函数做了一件事情:让更多内存可用、安装另一个new_handler、卸除new_handler、抛出bad_alloc异常(或其派生物)、或是承认失败而直接return。现在,对于new_handler为什么必须做出上述其中某些事情应该很清楚了,因为不那么做operator new内的while循环永远不会结束

    如果发生了继承,会发生分配大小错误

    class Base{
    static void* operator new(std::size_t size) throw(std::bad_alloc);
    ...
    }
    class Derived:public Base
    {...};
    Derived* p=new Derived;   //这里调用的是Base::operator new
               
    处理此情势的最佳做法是将“内存申请量错误”的调用行为改采用标准operator new,像这样:
    void* Base::operator new(std::size_t size) throw(std::bad_alloc)
    	{
    		using namespace std;
    		if(size!==sizeof(Base))
    			return ::operator new(size);
    		...
    	}
               
  4. 重写operator delete需要记住的唯一事情就是C++保证“删除null指针永远安全”,所以应该这样写:
    void operator delete(void* rawMemory) throw()
       	{
       		if(rawMemory==0) return;
       		现在,归还rawMemory所指的内存
       	}
       ```
       ```javascript 
       	void* Base::operator delete(void* rawMemory,std::size_t size) throw()
       	{
       		using namespace std;
       		if(rawMemory==0) return;
       		if(size!==sizeof(Base))
       			return ::operator delete(rawMemory);
       		现在,归还rawMemory所指的内存
       		...
       	}
       ```
               
  5. 写了placement new也要写placement delete

    如果operator new接受的参数除了一定会有的那个size_t之外还有其他,这便是个所谓的placement new。如果一个带额外参数的operator new没有“带相同额外参数”的对应版本的operator delete,那么当new的内存分配动作需要取消并恢复旧观时就没有任何operator delete会被调用,会发生内存泄漏(分配内存时的operator new调用成功,类类型的构造函数抛出异常的情况下)

    placement delete只有在“伴随placement new调用而触发的构造函数”出现异常时才会被调用。对着一个指针施行delete绝不会导致调用placement delete 如果对所有与placement new相关的内存泄漏宣战,我们必须同时提供一个正常的operator delete(用于构造期间无任何异常被抛出)和一个placement版本(用于构造期间有异常被抛出)

  6. 由于成员函数的名称会掩盖其外围作用域中的相同名称,即使参数列表不同,所以需要小心避免让class专属的news掩盖客户期望的其他news(包括正常版本)。假设一个base class,其中声明唯一一个placement operator new,客户端会发现他们无法使用正常形式的new:
    class Base{
    public:
        ...
    	 static void* operator new(std::size_t size,std::ostream& logStream) throw(std::bad_alloc);   //这个new会掩盖正常的形式
    	...
    };
    Base* pb=new Base;         //错误,因为正常形式的operator new被掩盖
    Base* pb=new (std::cerr) Base;   //正确,调用Base的placement new
       ```
       同样,derived classes中的operator new会掩盖global版本和继承而来的operator new版本