《Effective C++》学习笔记(条款51:编写 new 和 delete 时需固守常规)

最近开始看《Effective C++》,为了方便以后回顾,特意做了笔记。若本人对书中的知识点理解有误的话,望请指正!!!

条款50 讨论了什么时候需要自定义 operator new 和 operator delete,现在本条款来讨论在自定义时需要遵守的规则。

从operator new开始:

  • 实现一致性 operator new 必须返回正确的值
  • 内存不足时必须调用 new-handling 函数(见条款49)
  • 必须有对付零内存需求的准备
  • 避免不慎掩盖正常形式的 new

这比较偏近 class 接口的要求而非实现要求。正常形式的 new 描述于条款 52。

operator new 的返回值十分单纯。如果申请内存成功,就返回指向那块内存的指针,失败则遵循条款 49描述的规则,并抛出 bad_alloc 异常。

然而也不是非常单纯。因为 operator new 实际上不止一次尝试分配内存,并在每次失败后都调用 new-handling 函数。这里假设 new-handling 函数能做某些动作将一些内存释放出来。只有当指向 new-handling 函数的指针为 null,operator new 才会抛出异常。但C++规定,即使客户要求0 byte,operator new 也要返回一个合法指针。下面是个non-member operator new的伪代码:

void* operator new(std::size_t size) throw(std::bad_alloc) {
    using namespace std;
    if(size == 0)	//处理0-byte申请,将它视为 1 byte 申请
        size = 1;
    
    while(true) {
        尝试分配size bytes;
        if(分配成功)
            return 指向分配得来的内存的指针;
           
        //分配失败,找到当前的 new-handling 函数
        new_handler globalHandler = set_new_handler(0);
        set_new_handler(globalHandler);

        if(globalHandler) 
           (*globalHandler)();	//执行函数指针globalHandler指向的函数
        else 
           throw std::bad_alloc();
    }
}

对于 0 byte 的内存申请视为 1 byte 的内存申请,做法简单、合法、可行。其中将 new-handling 函数指针设为 null 而后又立刻恢复原样,是因为我们没有任何办法可以直接取得 new-handling 函数指针,所以利用 set_new_handler 函数的返回值是前一个 new-handling 函数指针的特性。这种方法在单线程环境下很有效,但在多线程环境下,还需要某种锁机制,以便处理 new-handling 函数背后的(global)数据结构。

条款49提到 operator new 内含一个无穷循环,而上述代码中的第 6 行(while(true))就是那个无穷循环。退出此循环唯一办法是:内存分配成功或 new-handling 函数做了一件描述于条款49的事:让更多内存可用、安装另一个 new-handler、卸载 new-handler、抛出 bad_alloc异常(或其派生类),或承认失败直接 return。

上面的 operator new 成员函数可能会被derived classes继承。注意分配内存大小size,它是函数接收的实参。条款50提到,定制内存分配器往往是为了特定的 class 对象,以此来优化,而不是为了该 class 的任何 派生类。也就是说,针对 class X 而设计的 operator new ,其行为只为大小刚好为 sizeof(X) 的对象而设计。然而一旦被继承,有可能 基类 的 operator new 被调用用于分配 派生类 对象:

class Base{
public:
    static void* operator new(std::size_t size) throw(std::bad_alloc);
    ...
};
class Derived:public Base{ ... };	//假设派生类未定义 operator new

Derived* p = new Derived;			//这里调用了Base::operator new

如果 基类 专属的 operator new 并非被设计用来应对上述情况(实际上往往如此),处理这种情况的方法是:将“内存申请量错误”的调用行为改为标准 operator new,就像这样:

void* Base::operator new(std::size_t size) throw(std::bad_alloc) {
    if(size != sizeof(Base))			//如果大小错误
        return ::operator new(size);	//使用标准的 operator new
    ...									//否则在这处理
}

如果你打算控制 class 专属的 “arrays 内存分配行为”,那么你需要实现 operator new[](这个函数通常被称为 “array new”)。编写operator new[] 时,唯一要做的事就是分配一块未加工的内存,因为你无法对 array 之内迄今尚未存在的元素对象做任何事。甚至我们无法知道这个 array 含有多少个元素对象。首先你不知道每个对象多大,毕竟 基类 的 operator new[] 有可能经由继承被调用,将内存分配给 “元素为 派生类 对象” 的 array使用。

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

上面就是自定义 operator new 需要遵守的规矩。operator delete 情况更简单,你需要记住的唯一事情就是C++保证删除 null 指针永远安全。下面是 non-member operator delete的伪代码:

void operator delete(void* rawMemory) throw(){
    if(rawMemory == 0) return;	//如果被删除的是个 null 指针,那就什么都不做

   现在, 归还 rawMemory 所指内存;
}

这个函数的 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) 					//检测是否为 null 指针
        return;
    if(size != sizeof(Base)) {				//如果大小错误,让标准 operator delete 处理此一申请
        ::operator delete(rawMemory);
        return;
    }
    现在,归还rawMemory所指内存;
    return ;
}

如果即将被删除的对象派生自某个 基类 ,而后者没有 虚析构函数,那么 C++ 传给 operator delete 的 size_t 数值可能不正确。也就是说,如果 基类 遗漏 虚析构函数,operator delete 可能无法正常运作。

Note:

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

条款52:写了placement new 也要写 placement delete

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值