首页 > 代码库 > Effective C++ 条款 51:编写new和delete时需固守常规

Effective C++ 条款 51:编写new和delete时需固守常规

(一)

实现一致性operator new必须返回正确的值,内存不足时必须调用new_handling函数,必须有对付零内存的准备,还需要避免不慎掩盖正常形式的new。

void* operator new(std::size_t size) throw(std::bad_alloc) { 
    using namespace std; 
    if (size == 0) { 
        size = 1; 
    } 
    while (true) { 
        尝试分配size byte 
        if (分配成功) 
            return (一个指针,指向分配得来的内存) 
        // 分配失败 
        new_handler globalHandler = set_new_handler(0); 
        set_new_handler(globalHandler);

        if (globalHandler) (*globalHandler)(); 
        else throw std::bad_alloc(); 
    } 
}
没有任何办法直接取得new_handling函数指针,所以必须调用set_new_handler找它出来。拙略,但有效——至少对单线程程序而言,多线程或许需要某种机锁以便安全处置new_handling函数背后的global数据结构。

operator new有个无穷循环,退出次循环的唯一办法是:内存成功分配或new_handling函数做了一件描述于条款49的事情:让更多内存可用、安装另一个new_handler、卸除new-handler、抛出bad_alloc异常、或者承认失败而直接return。


(二)

operator new成员函数会被derived classes继承。如果针对class X而设计的operator new,只为大小为sizeof(X)的对象而设计。一旦被继承下去,base class的operator new被调用用以分配derived class对象:

class Base { 
public: 
    static void* operator new(std::size_t size) throw(std::bad_alloc); 
};

class Derived : public Base { 
    ... 
}; 
Derived* p = new Derived;        //调用base::operator new
如果base class专属的operator new并非被设计用来对付上述情况,处理此情势最佳做法是:“内存申请量错误”的调用改采用标准operator new:

void* Base::operator new(std::size_t size) throw(std::bad_alloc) 
{ 
    if (size != sizeof(Base)) 
        return ::operator new(size); 
    ... 
}
如果size 为0,这份申请转交到::operator new手上,因为sizeof(Base)的大小不可能为0。

如果打算控制class专属“array内存分配行为”,那么你要实现operator new的array兄弟版;operator new[]。通常被称为“array new”,如果决定写个operator new[],唯一要做的一件事就是分配一块未加工内存,因为你无法对array之内迄今尚未存在的元素对象做任何事情。实际上,你甚至无法计算那个array将含多少个元素对象。你不知道每个对象多大,毕竟base class的operator new[]有可能经由继承被调用,将内存分配给“元素为derived class对象”的array使用。

因此,你不能在Base::operator new[] 内假设array的每个元素对象的大小是sizeof(base),这也意味着你不能假设array的元素个数是(byte申请数)/sizeof(base)。此外,传递给operator new[]的size_t参数,其值有可能比“将被填以对象”的内存数量多,条款16说过,动态分配的arrays可能包含额外空间用来存放元素个数。


(三)

operator delete的情况更简单,只要记住唯一的事情就是C++保证删除null指针永远安全,所以你必须兑现这项保证。

下面是non-member operator delete的伪码:

void operator delete(void *rawMemory) throw() { 
    if (rawMemory == 0) return; 
    现在,归还rawMemory所指内存 
}

下面是member operator delete,member版本也简单,只需多加一个动作检查删除数量。

万一你的class专属operator new将大小有误的分配转交::operator new执行,你必须也将大小有误的删除行为转交::operator delete执行:

class Base {
public:
	static void* operator new(std::size_t size) throw(std::bad_alloc);
	static void operator delete(void* rawMemory, std::size_t size) throw();
};

void Base::operator delete(void* rawMemory, std::size_t size) throw() {
	if(rawMemory == 0) return 0;
	if(size != sizeof(Base)) {
		::operator delete(rawMemory);	
		return;
	}
	现在,归还rawMemory所指的内存;
	return ;
}
如果即将被删除的对象派生自某个base class而后者欠缺virtual析构函数,那么c++传给operator delete的size_t数值可能不正确。

此时,operator delete可能无法正确运作。


请记住:

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

(2) operator delete应该在收到null指针时不做任何事情。class专属版本则还应该处理“比正确大小更大的(错误)申请”。









Effective C++ 条款 51:编写new和delete时需固守常规